List Workspaces
curl --request GET \
--url https://api.sublay.io/v7/:projectId/workspaces \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.sublay.io/v7/:projectId/workspaces"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.sublay.io/v7/:projectId/workspaces', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sublay.io/v7/:projectId/workspaces",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.sublay.io/v7/:projectId/workspaces"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.sublay.io/v7/:projectId/workspaces")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sublay.io/v7/:projectId/workspaces")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyWorkspace Endpoints
List Workspaces
List the caller’s direct-membership + owned workspaces
List Workspaces
curl --request GET \
--url https://api.sublay.io/v7/:projectId/workspaces \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.sublay.io/v7/:projectId/workspaces"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.sublay.io/v7/:projectId/workspaces', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sublay.io/v7/:projectId/workspaces",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.sublay.io/v7/:projectId/workspaces"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.sublay.io/v7/:projectId/workspaces")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sublay.io/v7/:projectId/workspaces")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyReturns a paginated list of workspaces scoped to the caller’s direct memberships + owned workspaces (both index-backed, no ancestor climb).
A service/master key called this route without naming an
A plain user token sent an
This route declares
See also: useFetchManyWorkspaces · useFetchManyWorkspacesWrapper
This is deliberately not an “everything I can reach” tree listing — that per-row reach resolution is deferred. Use this for the common “my workspaces” UI call.
Query Parameters
number
Page number for pagination.
number
Page size.
string
Comma-separated include flags.
include=memberCount adds a memberCount to every row — the workspace’s direct member count, the same number Fetch Workspace returns for one workspace, counting neither descendants nor reach-holders.It is resolved with a single grouped query over the page that was returned, so a workspace switcher showing counts costs one request rather than one per row. Omit the flag and the field is absent from each row — not null, not 0. An unrecognized flag value is ignored rather than rejected.string
required
Service/master keys only — the user to act as, whose workspaces are listed. Required for a key: this route has no unbounded path — the list is defined as somebody’s memberships — so a key that names nobody is refused with
400 workspace/missing-user-id.A plain user token omits it — the actor is the token’s own user. The field is not ignored for such a token: sending your own user id is a harmless no-op, but sending anyone else’s is a 403 workspace/unauthorized. See Acting on behalf of a user.Response
Returns a paginated response of Workspace objects.GET /v7/:projectId/workspaces?page=1&limit=10&include=memberCount
{
"data": [
{
"id": "0f1c7a6e-9d4b-4f2a-8c31-5b6e2a7d1c04",
"name": "Acme HQ",
"metadata": {},
"ownerId": "3a9e5c11-72d8-4a6b-9f10-8c2d4e7b5a33",
"parentWorkspaceId": null,
"depth": 0,
"inheritsFromParent": false,
"createdAt": "2026-01-14T09:12:44.001Z",
"updatedAt": "2026-01-14T09:12:44.001Z",
"memberCount": 12
}
],
"pagination": { "page": 1, "pageSize": 10, "totalPages": 1, "totalItems": 1, "hasMore": false }
}
memberCount appears only when include=memberCount was sent. limit defaults to 20 when omitted, and page to 1.
Error Responses
Missing Acting User — 400
Missing Acting User — 400
{ "error": "Missing user ID", "code": "workspace/missing-user-id" }
actingUserId. The listing is scoped to a user’s own memberships and owned workspaces, so there is nobody to scope it to.Acted as Another User — 403
Acted as Another User — 403
{ "error": "Unauthorized", "code": "workspace/unauthorized" }
actingUserId naming a different user. Only a service/master key may act as someone else.Undeclared Query Parameter — 400
Undeclared Query Parameter — 400
{ "error": "Unrecognized key: \"sort\"", "code": "workspace/invalid-query" }
page, limit, include, actingUserId and projectId, and refuses anything else rather than ignoring it. Two or more at once are named together: Unrecognized keys: "sort", "order". The two exceptions across the bundle are the cache-busters _ and _t, which are dropped before the request is read. See Shapes the server rejects.
