# Workspaces

> The Workspaces operations of the Hub REST API (12 operations).

URL: https://docs.synap.live/docs/reference/hub-api/workspaces · Generated from Synap-core/backend@c31e261ab (main)

Paths are relative to `/api/hub` on a pod.

## GET /workspaces

List accessible workspaces

Responses:

- `200`: Workspaces accessible to the authenticated user
- `403`: Forbidden
- `500`: Internal error

## GET /workspaces/{workspaceId}

Get a workspace by id

Returns the workspace plus usedByProjectIds (projects that use this domain via the project --uses--> workspace INDEX). Floored to projects the caller can see. Not an ACL.

Responses:

- `200`: Workspace
- `403`: Forbidden
- `404`: Not found
- `500`: Internal error

## GET /workspaces/{workspaceId}/eve-provider-routing

Read Eve provider routing policy

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | path | string | yes |  |

Responses:

- `200`: Provider routing policy
- `403`: Forbidden — not a member
- `404`: Workspace not found
- `500`: Internal error

## PATCH /workspaces/{workspaceId}/eve-provider-routing

Update Eve provider routing policy

Persists the workspace's AI provider routing policy. Owner/admin role required.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | path | string | yes |  |

Request body (`application/json`): EveProviderRouting

| Field | Type | Required | Description |
|---|---|---|---|
| `mode` | "local" \| "provider" \| "hybrid" |  |  |
| `defaultProvider` | "ollama" \| "openrouter" \| "anthropic" \| "openai" |  |  |
| `fallbackProvider` | "ollama" \| "openrouter" \| "anthropic" \| "openai" |  |  |
| `providers` | array<object> |  |  |
| `syncToSynap` | boolean |  |  |

Responses:

- `200`: Updated policy
- `400`: Bad request
- `403`: Forbidden — must be owner/admin
- `500`: Internal error

## PATCH /workspaces/{workspaceId}/source-edges

Declare workspace data edges (sourceRoles / defaultSources)

Set/merge the workspace-EDGE fields on an existing workspace: `sourceRoles` (domain → provider|consumer|provider-consumer) and `defaultSources` (domain → source workspace to read from). Merges per-domain — existing domains and all other settings are preserved, never clobbered. Editor+ membership required. GOVERNED: rewiring cross-workspace read routing goes through review — an agent caller gets `{ status: 'proposed', proposalId }` (applied on approval); an operator-authority caller applies immediately (`{ status: 'updated' }`).

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | path | string | yes |  |

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `sourceRoles` | object |  |  |
| `defaultSources` | object |  |  |

Responses:

- `200`: Merged edge fields
- `400`: Bad request
- `403`: Forbidden — not an editor+ member
- `500`: Internal error

## PATCH /workspaces/{workspaceId}/public-projection

Set workspace public-projection config

Set the workspace's public-projection config: `enabled` (master switch), `roles` (facet role-profile slugs whose holders are public) and `fields` (property-key allowlist for a projected row). Writes ONLY the `publicProjection` settings key — all other settings are preserved, never clobbered. Editor+ membership required. GOVERNED: opting into an unauthenticated public surface goes through review — an agent caller gets `{ status: 'proposed', proposalId }` (applied on approval); an operator-authority caller applies immediately (`{ status: 'updated' }`).

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | path | string | yes |  |

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `enabled` | boolean | yes |  |
| `roles` | array<string> | yes |  |
| `fields` | array<string> |  |  |

Responses:

- `200`: Applied public-projection config
- `400`: Bad request
- `403`: Forbidden — not an editor+ member
- `500`: Internal error

## GET /workspaces/{workspaceId}/governance

Read workspace governance policy

Returns the effective RBAC + auto-approve list for the workspace. Membership required.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | path | string | yes |  |

Responses:

- `200`: Governance policy
- `400`: Bad request
- `403`: Forbidden — not a member
- `500`: Internal error

## GET /workspaces/{workspaceId}/digest

Workspace data digest

Deterministic briefing of a workspace's existing data so an agent can understand it before acting: entity counts per profileSlug, the most recently-updated entities, and a one-line prose summary (no LLM).

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | path | string | yes |  |

Responses:

- `200`: Workspace digest
- `400`: Bad request
- `403`: Forbidden — not a member
- `404`: Workspace not found
- `500`: Internal error

## GET /users/{userId}/context

Get a user's context

Returns workspaces + active workspace for a given user (used by IS to bootstrap session context).

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `userId` | path | string | yes |  |

Responses:

- `200`: User context
- `400`: Bad request
- `403`: Forbidden
- `404`: Not found
- `500`: Internal error

## POST /workspaces/enroll-agent

Enroll an agent user into the caller's workspaces

Adds the given agent user as a member to all workspaces the calling user belongs to (or just one, when workspaceId is supplied). Idempotent — safe to call on reconnect. Used by `synap connect` when 'All workspaces' is selected to give the agent key pod-wide access.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `agentUserId` | string | yes |  |
| `workspaceId` | string |  |  |
| `role` | "viewer" \| "editor" \| "admin" |  |  |

Responses:

- `200`: List of workspace IDs the agent was enrolled into
- `400`: Invalid body or agentUserId not an agent user
- `403`: Forbidden
- `500`: Internal error

## POST /workspaces/provision-agent

Provision an agent-owned workspace (idempotent)

Idempotently provisions a workspace owned by an agent user. Sets wide autoApproveFor so the agent can write freely within it. The calling user is added as admin so they can see the agent's work. Requires hub-protocol.write scope.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `agentUserId` | string | yes |  |
| `workspaceName` | string |  |  |
| `idempotencyKey` | string |  |  |

Responses:

- `200`: Workspace id and creation outcome
- `400`: Invalid body or agentUserId not an agent user
- `403`: Forbidden — insufficient scope
- `500`: Internal error

## POST /workspaces/from-definition

Create a workspace from a WorkspaceProposal definition

Idempotently provisions a workspace from a WorkspaceProposal-shaped definition. Restricted to agent users whose agentType is in the allowlist (currently `eve`, `coder`). Pass a stable `proposalId` to make retries idempotent — the same id for the same user returns the existing workspace with `created: false`.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `proposalId` | string |  |  |
| `workspaceName` | string |  |  |
| `templateId` | string |  |  |
| `templateName` | string |  |  |
| `workspaceType` | "personal" \| "agent" \| "project" \| "operational" |  |  |
| `ownerUserId` | string |  |  |

Responses:

- `200`: Workspace id and creation outcome
- `400`: Invalid definition
- `403`: Forbidden — agentType not in allowlist or insufficient scope
- `500`: Internal error
