Reference
Error Catalog
Neuroloom's API returns two error shapes depending on when the error family was added. Both carry a stable code string a client can branch on — check code, not the human-readable message text, which may change without notice.
Legacy shape (most existing errors): {"detail": "...", "code": "..."}.
Current shape (workspace resolution errors, this page's subject, and the standard for new error families going forward): a nested {"error": {...}} envelope with no detail wrapper at all.
This page documents the three workspace-resolution errors introduced by OAuth workspace simplification. They fire when the server can't determine which workspace an authenticated request should operate on.
Why these errors exist
An OAuth-authenticated request no longer carries a workspace inside its access token — the token proves who you are, not which workspace you're working in. Every request picks a workspace fresh: send X-Workspace-Id to name one explicitly, or omit it and let the server auto-resolve when you belong to exactly one workspace. These three errors cover the cases where the server can't make that call for you. See Per-Project Workspace Routing for the full mental model.
API key authentication is unaffected — API keys are workspace-scoped by their key binding and never hit these errors.
workspace_not_specified
HTTP status: 400 Bad Request
Raised when an OAuth-authenticated request omits X-Workspace-Id and the caller belongs to two or more active workspaces. The server has no way to guess which one you mean.
{
"error": {
"code": "workspace_not_specified",
"message": "You belong to 3 workspaces. Specify which one with the X-Workspace-Id header, or update your MCP/API client config to include it.",
"request_id": "5f9e1c2a-...",
"workspaces": [
{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "personal-blog" },
{ "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "name": "company-api" },
{ "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "name": "oss-library" }
],
"hints": {
"claude_code_plugin": "Run `/neuroloom:init` (or restart your session) — the plugin re-resolves and writes X-Workspace-Id to .mcp.json automatically once your membership is unambiguous.",
"manual_mcp_config": "Add \"X-Workspace-Id\": \"<id>\" to the headers field in your MCP client config, using one of the workspace IDs listed above.",
"rest_api": "Send the X-Workspace-Id header on every request, set to one of the workspace IDs listed above."
},
"setup_url": "https://neuroloom.dev/docs/guides/multi-workspace"
}
}Fix by client:
| Client | Fix |
|---|---|
| Claude Code plugin | Run /neuroloom:init or restart your session — the plugin re-resolves automatically once membership is unambiguous and writes the result to .mcp.json. |
| Manual MCP config (Cursor, VS Code, Windsurf) | Add an X-Workspace-Id header to your client's config, set to one of the IDs in workspaces. See Per-Project Workspace Routing. |
| REST API / cURL | Send X-Workspace-Id on every request, set to one of the IDs in workspaces. |
no_workspace_membership
HTTP status: 400 Bad Request
Raised when an OAuth-authenticated request omits X-Workspace-Id and the caller belongs to zero active workspaces. This is a 400, not a 403 — the server is only ever reporting the authenticated caller's own membership count, never a third party's, so there's no cross-tenant information to protect by using 403's unified-denial convention.
{
"error": {
"code": "no_workspace_membership",
"message": "You don't belong to any workspace yet. Create one at the Neuroloom dashboard, then retry — the X-Workspace-Id header isn't needed until you have at least one membership.",
"request_id": "5f9e1c2a-...",
"workspaces": [],
"hints": {
"claude_code_plugin": "Create a workspace at the Neuroloom dashboard, then restart your session — the plugin will pick it up automatically.",
"manual_mcp_config": "Create a workspace at the Neuroloom dashboard, then set the X-Workspace-Id header to its ID in your MCP client config.",
"rest_api": "Create a workspace at the Neuroloom dashboard before calling any workspace-scoped endpoint."
},
"setup_url": "https://neuroloom.dev/docs/guides/multi-workspace"
}
}workspaces is always empty for this error — there's nothing to list.
Fix by client:
| Client | Fix |
|---|---|
| Claude Code plugin | Create a workspace at app.neuroloom.dev/settings/workspace, then restart your session — the plugin picks it up automatically. |
| Manual MCP config | Create a workspace at the dashboard, then set X-Workspace-Id to its ID in your client config. |
| REST API / cURL | Create a workspace at the dashboard before calling any workspace-scoped endpoint. |
This also fires from the OAuth consent screen — a zero-workspace user is blocked from completing consent (the approve branch only; denying consent always succeeds regardless of membership count).
invalid_workspace_id
HTTP status: 400 Bad Request
Raised when X-Workspace-Id is present but either isn't a valid UUID, or names a workspace the caller isn't an active member of. Both cases return the same code and status — the response never discloses whether a malformed value or a well-formed-but-foreign one caused the failure, which keeps this consistent with the rest of the API's non-enumeration behavior.
{
"error": {
"code": "invalid_workspace_id",
"message": "The X-Workspace-Id header is not valid — it must be a UUID matching one of your active workspace memberships.",
"request_id": "5f9e1c2a-...",
"workspaces": [
{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "personal-blog" }
],
"hints": {
"claude_code_plugin": "Run `/neuroloom:init` (or restart your session) to let the plugin re-resolve and overwrite the invalid X-Workspace-Id value.",
"manual_mcp_config": "Fix the X-Workspace-Id header in your MCP client config — it must exactly match one of the workspace IDs listed above.",
"rest_api": "Send a valid X-Workspace-Id header, set to one of the workspace IDs listed above."
},
"setup_url": "https://neuroloom.dev/docs/guides/multi-workspace"
}
}workspaces lists the caller's own active memberships — fetched specifically for this error, not carried over from an auto-resolve check that didn't run (the header was present, so auto-resolve never fires).
Fix by client:
| Client | Fix |
|---|---|
| Claude Code plugin | Run /neuroloom:init or restart your session — the plugin re-resolves and overwrites the invalid value. |
| Manual MCP config | Fix the X-Workspace-Id header in your client config so it exactly matches one of the IDs in workspaces. |
| REST API / cURL | Send a valid X-Workspace-Id header, set to one of the IDs in workspaces. |
Reading the envelope
Every field in the error object is stable and safe to parse programmatically:
| Field | Type | Description |
|---|---|---|
code | string | Stable machine-readable identifier. Branch on this, not on message. |
message | string | Human-readable explanation, self-sufficient without a docs lookup — it names the membership count and the fix. |
request_id | string (UUID) | Matches the response's X-Request-ID header. Include this when filing a support request. |
workspaces | array of {id, name} | The caller's own active workspace memberships, scoped to this specific error. Empty for no_workspace_membership. |
hints | object | Per-client next-action text, keyed by claude_code_plugin, manual_mcp_config, rest_api. |
setup_url | string (URL) | Links to Per-Project Workspace Routing. |
Filing a bug or support request about one of these errors? Include the request_id — it lets Neuroloom's team find the exact request in server logs.
Related
- Per-Project Workspace Routing — how to configure
X-Workspace-Idacross MCP clients - Workspace Isolation — the underlying workspace/membership model
- MCP Tools Reference — troubleshooting table for MCP-specific connection errors