# Workspace API Reference

> Workspace management REST API reference — create, list, update, and delete MCP workspaces. All endpoints require a JWT or account API key.

All workspace management endpoints are mounted at `/api/community/mcp/workspaces`. Every request requires either a JWT Bearer token or an `X-API-Key` account key.

---

## Authentication

All routes accept two authentication methods:

| Method | Header | Format |
|--------|--------|--------|
| JWT Bearer | `Authorization` | `Bearer eyJhbGci...` |
| Account API Key | `X-API-Key` | `ak_...` |

Unauthenticated requests return `401 Unauthorized`.

---

## Workspaces

### Create Workspace

```
POST /api/community/mcp/workspaces
```

**Request Body:**

```json
{
  "name": "My Workspace",
  "description": "Optional description"
}
```

| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `name` | string | Yes | Min 2 characters |
| `description` | string | No | Free-form text |

**Response (201):**

```json
{
  "id": "uuid",
  "slug": "my-workspace",
  "name": "My Workspace",
  "description": "Optional description",
  "gateway_url": "https://mcp.aerostack.dev/ws/my-workspace",
  "created_at": 1710000000
}
```

**Errors:**

| Status | Error | Cause |
|--------|-------|-------|
| 400 | `name is required (min 2 characters)` | Missing or too short name |
| 403 | `MCP workspace limit reached...` | Plan workspace limit exceeded |

---

### List Workspaces

```
GET /api/community/mcp/workspaces
```

**Response (200):**

```json
{
  "workspaces": [
    {
      "id": "uuid",
      "slug": "my-workspace",
      "name": "My Workspace",
      "description": "...",
      "status": "active",
      "server_count": 3,
      "gateway_url": "https://mcp.aerostack.dev/ws/my-workspace",
      "created_at": 1710000000,
      "updated_at": 1710000000
    }
  ]
}
```

---

### Get Workspace

```
GET /api/community/mcp/workspaces/:id
```

Returns the workspace with all its servers (joined with MCP server details).

**Response (200):**

```json
{
  "id": "uuid",
  "slug": "my-workspace",
  "name": "My Workspace",
  "description": "...",
  "status": "active",
  "gateway_url": "https://mcp.aerostack.dev/ws/my-workspace",
  "servers": [
    {
      "ws_server_id": "link-uuid",
      "server_id": "server-uuid",
      "slug": "@acme/github",
      "name": "GitHub Tools",
      "description": "GitHub integration",
      "type": "skill",
      "hosted": true,
      "access_type": "public",
      "star_count": 42,
      "tool_count": 5,
      "is_function": false,
      "inject_secrets": ["GITHUB_TOKEN"],
      "enabled": true,
      "display_order": 0
    }
  ],
  "created_at": 1710000000,
  "updated_at": 1710000000
}
```

---

### Update Workspace

```
PATCH /api/community/mcp/workspaces/:id
```

**Request Body (all fields optional):**

```json
{
  "name": "Updated Name",
  "description": "Updated description"
}
```

| Field | Type | Constraints |
|-------|------|-------------|
| `name` | string | Min 2 characters |
| `description` | string | Free-form text |

**Response (200):**

```json
{ "success": true }
```

  Updating a workspace name does not change the slug. The slug is immutable.

---

### Delete Workspace

```
DELETE /api/community/mcp/workspaces/:id
```

Deletes the workspace and all associated server links, tokens, and secrets.

**Response (200):**

```json
{ "success": true }
```

---

## Servers

### Add Server to Workspace

```
POST /api/community/mcp/workspaces/:id/servers
```

**Request Body:**

```json
{
  "server_id": "uuid",
  "inject_secrets": ["SECRET_KEY_1", "SECRET_KEY_2"],
  "display_order": 0
}
```

Or, to add a community function (auto-wrapped as a skill):

```json
{
  "function_id": "uuid"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `server_id` | string | One of `server_id` or `function_id` | Published MCP server UUID |
| `function_id` | string | One of `server_id` or `function_id` | Deployed community function UUID |
| `inject_secrets` | string[] | No | Secret key names to inject at call time |
| `display_order` | number | No | Sort order (0-9999, lower first) |

**Response (201):**

```json
{
  "ws_server_id": "link-uuid",
  "server_id": "resolved-uuid",
  "name": "Server Name",
  "slug": "@owner/server-slug"
}
```

**Errors:**

| Status | Error | Cause |
|--------|-------|-------|
| 400 | `server_id or function_id is required` | Neither provided |
| 403 | `This skill is private...` | Attempting to add a private server you do not own |
| 404 | `MCP server not found or not published` | Server does not exist or is not published |
| 409 | `Server is already in this workspace` | Duplicate server link |
| 422 | `This function is not deployed yet...` | Function must be deployed before adding |

---

### Update Server in Workspace

```
PATCH /api/community/mcp/workspaces/:id/servers/:wsServerId
```

**Request Body (all fields optional):**

```json
{
  "inject_secrets": ["GITHUB_TOKEN"],
  "display_order": 2,
  "enabled": true
}
```

| Field | Type | Description |
|-------|------|-------------|
| `inject_secrets` | string[] | Secret key names to inject |
| `display_order` | number | Sort order (0-9999) |
| `enabled` | boolean | Enable or disable the server |
| `disabled` | boolean | Inverted alias for `enabled` |

**Response (200):**

```json
{ "success": true }
```

---

### Remove Server from Workspace

```
DELETE /api/community/mcp/workspaces/:id/servers/:wsServerId
```

**Response (200):**

```json
{ "success": true }
```

---

### Test Server Connectivity

```
POST /api/community/mcp/workspaces/:id/servers/:wsServerId/test
```

Sends a test request to the upstream server to verify connectivity and list available tools.

**Response (200 -- success):**

```json
{
  "ok": true,
  "tools_count": 5,
  "tools": ["create_issue", "list_repos", "search_code", "get_pr", "merge_pr"]
}
```

**Response (200 -- failure):**

```json
{
  "ok": false,
  "error": "HTTP 401: Unauthorized"
}
```

---

## Tokens

### Issue Token

```
POST /api/community/mcp/workspaces/:id/tokens
```

**Request Body:**

```json
{
  "name": "Alice Cursor",
  "expires_in": 2592000
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Human-readable label |
| `expires_in` | number | No | Seconds until expiry |
| `expiry` | number | No | Alias for `expires_in` |

**Response (201):**

```json
{
  "id": "token-uuid",
  "token": "mwt_7a3f9c...",
  "name": "Alice Cursor",
  "expires_at": 1712592000,
  "mcp_json": {
    "mcpServers": {
      "my-workspace": {
        "url": "https://mcp.aerostack.dev/ws/my-workspace",
        "headers": { "Authorization": "Bearer mwt_7a3f9c..." }
      }
    }
  },
  "warning": "Save this token now. It will not be shown again."
}
```

  The `token` field is returned **only in this response**. It cannot be retrieved later — save it immediately.

---

### List Tokens

```
GET /api/community/mcp/workspaces/:id/tokens
```

**Response (200):**

```json
{
  "tokens": [
    {
      "id": "token-uuid",
      "name": "Alice Cursor",
      "active": true,
      "last_used_at": 1710500000,
      "created_at": 1710000000,
      "revoked_at": null,
      "expires_at": 1712592000
    }
  ]
}
```

---

### Revoke Token

```
DELETE /api/community/mcp/workspaces/:id/tokens/:tokenId
```

Sets `revoked_at` timestamp. The token stops working immediately.

**Response (200):**

```json
{ "success": true }
```

---

## Secrets

### List Secrets

```
GET /api/community/mcp/workspaces/:id/secrets
```

Returns secret names only. Values are never returned.

**Response (200):**

```json
{
  "secrets": [
    {
      "id": "secret-uuid",
      "key_name": "GITHUB_TOKEN",
      "key": "GITHUB_TOKEN",
      "created_at": 1710000000
    }
  ]
}
```

---

### Create or Update Secret

```
POST /api/community/mcp/workspaces/:id/secrets
```

**Request Body:**

```json
{
  "key": "GITHUB_TOKEN",
  "value": "ghp_abc123..."
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `key` | string | Yes | Key name (auto-uppercased, sanitized to `[A-Z0-9_]`) |
| `key_name` | string | No | Alias for `key` |
| `value` | string | Yes | Secret value (encrypted at rest) |

Upsert behavior: if a secret with the same key name exists, the value is overwritten.

**Response (201):**

```json
{
  "id": "secret-uuid",
  "key": "GITHUB_TOKEN",
  "success": true
}
```

---

### Delete Secret

```
DELETE /api/community/mcp/workspaces/:id/secrets/:secretId
```

The `:secretId` parameter accepts either a UUID or a key name (e.g. `GITHUB_TOKEN`).

**Response (200):**

```json
{ "success": true }
```

**Errors:**

| Status | Error | Cause |
|--------|-------|-------|
| 404 | `Secret not found` | No secret matches the ID or key name |

---

## Gateway Endpoints

These endpoints are on the gateway, not the management API. They require a workspace token (`mwt_...`) instead of a JWT.

### MCP JSON-RPC Gateway

```
POST https://mcp.aerostack.dev/ws/:slug
Authorization: Bearer mwt_...
Content-Type: application/json
```

Supported methods: `initialize`, `ping`, `tools/list`, `tools/call`

### OpenAI Tools Adapter

```
GET https://mcp.aerostack.dev/ws/:slug/openai-tools
Authorization: Bearer mwt_...
```

### Gemini Tools Adapter

```
GET https://mcp.aerostack.dev/ws/:slug/gemini-tools
Authorization: Bearer mwt_...
```
