This document defines the Pod API shape that the App Host (VS Code-based shell) and ecosystem apps will call via the PodApiClient (pods:// URIs).
This is not intended to be a fully stable public API yet. It is a v0 internal contract to:
- Unblock the App Host and core apps (Mesh Explorer, Chat, etc.).
- Provide a coherent set of endpoints and JSON shapes.
- Stay aligned with security, context, and realm/pod isolation principles.
- Provide a consistent, simple HTTP/JSON API for each pod.
- Allow the App Host to talk to a pod via a small set of domains:
auth– sessions/tokens (later).context– who/where we are.apps– which apps are available on this pod (optional, later).chat– channels, messages, threads.mesh– realm/pod metadata, health.- (later)
forum,media,gov, etc.
- Map cleanly to the
pods://URI scheme used by the App Host:- e.g.
pods://chat/listChannels→GET /api/chat/channels.
- e.g.
For now, the implementation can be in-memory/mocked and later wired into real data stores and services.
- All pod APIs are served under
/api. - Versioning:
- For v0, endpoints are at
/api/.... - When stable, we can adopt
/api/v1/...while keeping v0 for internal use only.
- For v0, endpoints are at
- For this first iteration, it is acceptable to:
- Stub auth checks,
- Or use a simple header such as
Authorization: Bearer <token>and ignore validation.
- The API must be designed to expect auth in the future:
- All handlers should be structured so that adding auth checks later does not break the shape.
All endpoints should respond with a consistent JSON envelope:
{
"ok": true,
"error": null,
"data": { /* domain-specific payload */ }
}On failure:
{
"ok": false,
"error": {
"code": "SOME_CODE",
"message": "Human-readable error",
"details": { /* optional */ }
},
"data": null
}The App Host's PodApiClient expects this envelope.
Define a small initial set of error codes:
UNAUTHORIZEDFORBIDDENNOT_FOUNDVALIDATION_FAILEDINTERNAL_ERRORNOT_IMPLEMENTED
This can expand later as needed.
These endpoints give the client basic information about where it is and who it is.
Purpose: Return the active realm/pod/user context from this pod's perspective.
Mapped pods:// URI:
pods://mesh/getContext
Request:
- Method:
GET - Path:
/api/context - Auth: optional placeholder for now.
Response data shape:
{
"realm": {
"id": "slskdn-main-v1",
"name": "Main Realm"
},
"pod": {
"id": "pod-1234",
"name": "My Pod",
"roles": ["owner", "admin"] // roles relative to this pod
},
"user": {
"id": "user-abcdef",
"displayName": "Alice",
"roles": ["member"], // roles relative to this pod
"isF1000": false // if applicable in this realm
}
}This endpoint allows the App Host to:
- Initialize
MeshContext(realmId/podId/userId). - Render status bar indicators and basic identity info.
Later, pods may expose app manifests or capabilities.
Purpose: List apps/capabilities that this pod wants to expose to the client.
Mapped pods:// URI:
pods://mesh/listApps (for future use).
Request:
GET /api/apps
Response data shape (initial idea):
{
"apps": [
{
"appId": "mesh.chat",
"enabled": true
},
{
"appId": "mesh.mesh",
"enabled": true
}
]
}For now, the App Host can rely on local manifests. This endpoint can be wired in later to allow pods to publish app availability and configuration.
These endpoints support the "Mesh Explorer" app.
Purpose: Return a list of pods known to this pod in its realm (and optionally cross-realm, if applicable).
Mapped pods:// URI:
pods://mesh/listPods
Request:
GET /api/mesh/pods
Response data shape:
{
"realm": {
"id": "slskdn-main-v1",
"name": "Main Realm"
},
"pods": [
{
"id": "pod-1234",
"name": "My Pod",
"status": "online", // "online" | "offline" | "degraded" | "unknown"
"healthScore": 0.98, // optional float 0.0–1.0
"roles": ["self"] // e.g. ["self"], ["peer"], ["bridge"]
},
{
"id": "pod-5678",
"name": "Friend Pod",
"status": "online",
"healthScore": 0.87,
"roles": ["peer"]
}
]
}For now, return a mock list. Later, plug into the real mesh topology / HealthManager.
Purpose: Detailed health info for a specific pod.
Mapped pods:// URI:
pods://mesh/getPodHealth?podId=pod-1234
Request:
GET /api/mesh/pods/{podId}/health
Response data shape:
{
"podId": "pod-1234",
"status": "online",
"healthScore": 0.98,
"lastSeen": "2025-12-10T12:34:56Z",
"metrics": {
"latencyMs": 35,
"failureRate": 0.01
}
}Initial implementation can be fully mocked.
These endpoints back the Chat app surfaces.
Minimal shapes:
interface ChatChannel {
id: string;
name: string;
isPrivate: boolean;
topic?: string;
createdAt: string; // ISO timestamp
createdBy: string; // user id
}interface ChatMessage {
id: string;
channelId: string;
authorId: string;
authorDisplayName: string;
body: string;
createdAt: string; // ISO timestamp
editedAt?: string; // ISO timestamp
threadRootId?: string; // if this message is part of a thread
}Purpose: List chat channels available to the current user in this pod.
Mapped pods:// URI:
pods://chat/listChannels
Request:
GET /api/chat/channels
Response data shape:
{
"channels": [
{
"id": "chan-general",
"name": "#general",
"isPrivate": false,
"topic": "General discussion",
"createdAt": "2025-12-10T12:00:00Z",
"createdBy": "user-abcdef"
},
{
"id": "chan-mesh",
"name": "#mesh-dev",
"isPrivate": true,
"topic": "Mesh development",
"createdAt": "2025-12-10T12:05:00Z",
"createdBy": "user-abcdef"
}
]
}Initial implementation can return a static list.
Purpose: Fetch messages in a given channel, with pagination.
Mapped pods:// URI:
pods://chat/getChannelMessages?channelId=...
Request:
GET /api/chat/channels/{channelId}/messages- Query parameters:
before(optional): message ID or timestamp cursor.limit(optional): integer, default 50.
Response data shape:
{
"channel": {
"id": "chan-general",
"name": "#general"
},
"messages": [
{
"id": "msg-1",
"channelId": "chan-general",
"authorId": "user-abcdef",
"authorDisplayName": "Alice",
"body": "Hello world",
"createdAt": "2025-12-10T12:10:00Z"
},
{
"id": "msg-2",
"channelId": "chan-general",
"authorId": "user-ghijkl",
"authorDisplayName": "Bob",
"body": "Hi Alice",
"createdAt": "2025-12-10T12:11:00Z"
}
],
"paging": {
"hasMore": false,
"nextCursor": null
}
}For v0, you can ignore pagination and just return a fixed array with hasMore: false.
Purpose: Send a new message to a channel.
Mapped pods:// URI:
pods://chat/sendMessage
Request:
POST /api/chat/messages- JSON body:
{
"channelId": "chan-general",
"body": "Message text",
"threadRootId": "msg-1" // optional, if replying in a thread
}Response data shape:
{
"message": {
"id": "msg-3",
"channelId": "chan-general",
"authorId": "user-abcdef",
"authorDisplayName": "Alice",
"body": "Message text",
"createdAt": "2025-12-10T12:12:00Z",
"threadRootId": "msg-1"
}
}Initial implementation can:
- Accept any body,
- Echo back a message object with generated IDs and timestamps,
- Store in an in-memory list per pod process (no persistence needed in v0).
Purpose: Create a new channel.
Mapped pods:// URI:
pods://chat/createChannel
Request:
POST /api/chat/channels- JSON body:
{
"name": "#new-channel",
"isPrivate": false,
"topic": "Optional topic"
}Response data shape:
{
"channel": {
"id": "chan-new-channel",
"name": "#new-channel",
"isPrivate": false,
"topic": "Optional topic",
"createdAt": "2025-12-10T12:20:00Z",
"createdBy": "user-abcdef"
}
}For v0, you can:
- Validate that
nameis non-empty. - Append to an in-memory channels list.
The App Host's PodApiClient operates with pods:// URIs. The mapping in v0 should be:
pods://mesh/getContext→GET /api/contextpods://mesh/listPods→GET /api/mesh/podspods://mesh/getPodHealth?podId=X→GET /api/mesh/pods/X/healthpods://chat/listChannels→GET /api/chat/channelspods://chat/getChannelMessages?channelId=X→GET /api/chat/channels/X/messagespods://chat/sendMessage→POST /api/chat/messagespods://chat/createChannel→POST /api/chat/channels
The exact query parameter encoding for pods:// URIs is up to the App Host implementation; the pod-side only needs the HTTP routes.
Even in v0/stub form, adhere to these principles:
- No cross-realm impersonation:
- A pod should only report data about its own realm/pod and known peers as configured.
- Role awareness (future):
- The API shapes include
rolesfields so later we can enforce:- Who can create channels,
- Who can see private channels,
- etc.
- The API shapes include
- Error handling:
- All errors must be mapped to the response envelope and not leak internal stack traces.
As the mesh governance and MCP layers are wired in, additional checks will be added on top of these endpoints to enforce content and behavior policies.
For the first implementation pass, it is sufficient to:
- Implement all endpoints with in-memory, process-local data:
GET /api/context→ hard-coded context or simple config.GET /api/mesh/pods→ fixed mock list.GET /api/mesh/pods/:podId/health→ mock health payloads.GET /api/chat/channels→ in-memory list of channels.POST /api/chat/channels→ append to in-memory channels.GET /api/chat/channels/:channelId/messages→ in-memory messages per channel.POST /api/chat/messages→ append to in-memory messages.
This is enough to:
- Unblock the App Host implementation.
- Demonstrate tree views and webviews with real HTTP calls.
- Evolve toward persistent storage and real mesh/health data later.