Skip to content

ADR-0012: Workspace Context via Header, Not Path Variable

Status: ACCEPTED Date: 2026-03-23 Supersedes: N/A Superseded by: N/A

Context

The original API design used {workspaceId} as a path variable that served dual purpose: resource locator (which workspace to operate on) and permission context (where to check RBAC). This created ambiguity in admin endpoints where the user's own workspace (permission source) differs from the target workspace (resource being managed). A workaround flag validateMembership=false was used to bypass membership checks, weakening security.

Source: docs/archive/specs/2026-03-23-workspace-context-header-design.md

Decision

The user's current workspace (permission context) is transmitted via the X-Workspace-Id HTTP header, validated by @RequireWorkspace annotation processing. Path variables carry no business data. Target resource identifiers (e.g., a different workspace's workspaceBizId) go in the request body. Admin endpoints that previously used GET with path variables are converted to POST with body parameters. The validateMembership and validateStatus parameters are removed; membership and status are always enforced.

Consequences

  • Clean separation: header = "who am I acting as", body = "what am I acting on".
  • The validateMembership=false workaround is eliminated; all requests are fully validated.
  • Every endpoint with @RequirePermission must also have @RequireWorkspace, enforced by the gateway chain.
  • Admin GET endpoints become POST (non-RESTful but unambiguous), consistent with ADR-0005.
  • Frontend must include X-Workspace-Id header on all workspace-scoped requests.

Alternatives Considered

  • Dual path variables (/my-workspace/{myId}/target/{targetId}): Rejected because it makes URLs even longer and does not solve the semantic ambiguity.
  • JWT claims for workspace context: Rejected because workspace selection can change within a session (user switches workspaces), while the JWT is issued at login time.
  • Keep validateMembership=false with better documentation: Rejected because disabling membership validation is a security hole that documentation cannot fix.

Internal Handbook