openapi: 3.1.0
info:
  title: Workspaces API
  description: |
    Zanzibar-inspired workspace authorization service. Internal, service-to-service: callers present a bearer service credential; the acting user and subject are request fields. Connect protocol over HTTP POST + JSON.
  version: v1
  license:
    name: AGPL-3.0
servers:
  - url: http://localhost:8080
    description: Local docker-compose
security:
  - serviceCredential: []
components:
  securitySchemes:
    serviceCredential:
      type: http
      scheme: bearer
      description: A service credential from GATEWAY_SERVICE_AUTH_TOKENS.
  schemas:
    google.protobuf.NullValue:
      type: string
      title: NullValue
      enum:
        - NULL_VALUE
      description: |-
        `NullValue` is a singleton enumeration to represent the null value for the
         `Value` type union.

         The JSON representation for `NullValue` is JSON `null`.
    workspace.v1.EnrollmentState:
      type: string
      title: EnrollmentState
      enum:
        - ENROLLMENT_STATE_UNSPECIFIED
        - ENROLLMENT_STATE_WAITLISTED
        - ENROLLMENT_STATE_ENROLLED
        - ENROLLMENT_STATE_ACTIVE
        - ENROLLMENT_STATE_COMPLETED
        - ENROLLMENT_STATE_DROPPED
      description: |-
        ─── Enrollment lifecycle overlay ──────────────────────────────────────

         An enrollment overlays a lifecycle STATE on a group membership, so a group
         used as a cohort/class can track each member's progress. Only the
         access-bearing states (ENROLLED, ACTIVE) place the member in the group's
         `member` userset (the backing `group:<id>#member` tuple is present); the
         others (WAITLISTED, COMPLETED, DROPPED) record the state WITHOUT granting
         access (no tuple). Access is therefore revoked/granted purely by tuple
         presence — the same mechanism as workspace member suspend/reinstate — so
         Check/CheckSet over `group:<cohort>#member` naturally exclude a completed,
         dropped, or waitlisted enrollee. The enrollment row and its tuple move
         atomically. The overlay is additive: AddGroupMember/RemoveGroupMember still
         work for plain (un-tracked) membership.
    workspace.v1.InvitationStatus:
      type: string
      title: InvitationStatus
      enum:
        - INVITATION_STATUS_UNSPECIFIED
        - INVITATION_STATUS_PENDING
        - INVITATION_STATUS_ACCEPTED
        - INVITATION_STATUS_REVOKED
        - INVITATION_STATUS_EXPIRED
    workspace.v1.MembershipStatus:
      type: string
      title: MembershipStatus
      enum:
        - MEMBERSHIP_STATUS_UNSPECIFIED
        - MEMBERSHIP_STATUS_ACTIVE
        - MEMBERSHIP_STATUS_INVITED
        - MEMBERSHIP_STATUS_SUSPENDED
    workspace.v1.ProjectStatus:
      type: string
      title: ProjectStatus
      enum:
        - PROJECT_STATUS_UNSPECIFIED
        - PROJECT_STATUS_ACTIVE
        - PROJECT_STATUS_SUSPENDED
    workspace.v1.Role:
      type: string
      title: Role
      enum:
        - ROLE_UNSPECIFIED
        - ROLE_OWNER
        - ROLE_ADMIN
        - ROLE_MEMBER
        - ROLE_GUEST
      description: |-
        Role is the coarse membership grade. It maps 1:1 onto a relation in the
         `workspace` authz namespace, ordered owner ⊃ admin ⊃ member ⊃ guest.
    workspace.v1.TupleUpdate.Op:
      type: string
      title: Op
      enum:
        - OP_UNSPECIFIED
        - OP_INSERT
        - OP_DELETE
    workspace.v1.UsersetTree.NodeType:
      type: string
      title: NodeType
      enum:
        - NODE_TYPE_UNSPECIFIED
        - NODE_TYPE_UNION
        - NODE_TYPE_LEAF
        - NODE_TYPE_INTERSECTION
        - NODE_TYPE_EXCLUSION
    workspace.v1.WorkspaceType:
      type: string
      title: WorkspaceType
      enum:
        - WORKSPACE_TYPE_UNSPECIFIED
        - WORKSPACE_TYPE_PERSONAL
        - WORKSPACE_TYPE_TEAM
    google.protobuf.ListValue:
      type: object
      properties:
        values:
          type: array
          items:
            $ref: '#/components/schemas/google.protobuf.Value'
          title: values
          description: Repeated field of dynamically typed values.
      title: ListValue
      additionalProperties: false
      description: |-
        `ListValue` is a wrapper around a repeated field of values.

         The JSON representation for `ListValue` is JSON array.
    google.protobuf.Struct:
      type: object
      additionalProperties:
        $ref: '#/components/schemas/google.protobuf.Value'
      description: |-
        `Struct` represents a structured data value, consisting of fields
         which map to dynamically typed values. In some languages, `Struct`
         might be supported by a native representation. For example, in
         scripting languages like JS a struct is represented as an
         object. The details of that representation are described together
         with the proto support for the language.

         The JSON representation for `Struct` is JSON object.
    google.protobuf.Struct.FieldsEntry:
      type: object
      properties:
        key:
          type: string
          title: key
        value:
          title: value
          $ref: '#/components/schemas/google.protobuf.Value'
      title: FieldsEntry
      additionalProperties: false
    google.protobuf.Timestamp:
      type: string
      examples:
        - 1s
        - 1.000340012s
      format: date-time
      description: |-
        A Timestamp represents a point in time independent of any time zone or local
         calendar, encoded as a count of seconds and fractions of seconds at
         nanosecond resolution. The count is relative to an epoch at UTC midnight on
         January 1, 1970, in the proleptic Gregorian calendar which extends the
         Gregorian calendar backwards to year one.

         All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
         second table is needed for interpretation, using a [24-hour linear
         smear](https://developers.google.com/time/smear).

         The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
         restricting to that range, we ensure that we can convert to and from [RFC
         3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.

         # Examples

         Example 1: Compute Timestamp from POSIX `time()`.

             Timestamp timestamp;
             timestamp.set_seconds(time(NULL));
             timestamp.set_nanos(0);

         Example 2: Compute Timestamp from POSIX `gettimeofday()`.

             struct timeval tv;
             gettimeofday(&tv, NULL);

             Timestamp timestamp;
             timestamp.set_seconds(tv.tv_sec);
             timestamp.set_nanos(tv.tv_usec * 1000);

         Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.

             FILETIME ft;
             GetSystemTimeAsFileTime(&ft);
             UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;

             // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
             // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
             Timestamp timestamp;
             timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
             timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));

         Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.

             long millis = System.currentTimeMillis();

             Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
                 .setNanos((int) ((millis % 1000) * 1000000)).build();

         Example 5: Compute Timestamp from Java `Instant.now()`.

             Instant now = Instant.now();

             Timestamp timestamp =
                 Timestamp.newBuilder().setSeconds(now.getEpochSecond())
                     .setNanos(now.getNano()).build();

         Example 6: Compute Timestamp from current time in Python.

             timestamp = Timestamp()
             timestamp.GetCurrentTime()

         # JSON Mapping

         In JSON format, the Timestamp type is encoded as a string in the
         [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
         format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
         where {year} is always expressed using four digits while {month}, {day},
         {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
         seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
         are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
         is required. A proto3 JSON serializer should always use UTC (as indicated by
         "Z") when printing the Timestamp type and a proto3 JSON parser should be
         able to accept both UTC and other timezones (as indicated by an offset).

         For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
         01:30 UTC on January 15, 2017.

         In JavaScript, one can convert a Date object to this format using the
         standard
         [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
         method. In Python, a standard `datetime.datetime` object can be converted
         to this format using
         [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
         the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
         the Joda Time's [`ISODateTimeFormat.dateTime()`](
         http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()
         ) to obtain a formatter capable of generating timestamps in this format.
    google.protobuf.Value:
      oneOf:
        - type: "null"
        - type: number
        - type: string
        - type: boolean
        - type: array
        - type: object
          additionalProperties: true
      description: |-
        `Value` represents a dynamically typed value which can be either
         null, a number, a string, a boolean, a recursive struct value, or a
         list of values. A producer of value is expected to set one of these
         variants. Absence of any variant indicates an error.

         The JSON representation for `Value` is JSON value.
    workspace.v1.AcceptInvitationRequest:
      type: object
      properties:
        token:
          type: string
          title: token
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: AcceptInvitationRequest
      additionalProperties: false
    workspace.v1.AcceptInvitationResponse:
      type: object
      properties:
        membership:
          title: membership
          $ref: '#/components/schemas/workspace.v1.Membership'
      title: AcceptInvitationResponse
      additionalProperties: false
    workspace.v1.AddGroupMemberRequest:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        member:
          title: member
          $ref: '#/components/schemas/workspace.v1.GroupMember'
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: AddGroupMemberRequest
      additionalProperties: false
    workspace.v1.AddGroupMemberResponse:
      type: object
      title: AddGroupMemberResponse
      additionalProperties: false
    workspace.v1.AddMemberRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        userId:
          type: string
          title: user_id
        role:
          title: role
          $ref: '#/components/schemas/workspace.v1.Role'
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: AddMemberRequest
      additionalProperties: false
    workspace.v1.AddMemberResponse:
      type: object
      properties:
        membership:
          title: membership
          $ref: '#/components/schemas/workspace.v1.Membership'
      title: AddMemberResponse
      additionalProperties: false
    workspace.v1.AssignSeatRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        sku:
          type: string
          title: sku
        userId:
          type: string
          title: user_id
      title: AssignSeatRequest
      additionalProperties: false
    workspace.v1.AssignSeatResponse:
      type: object
      properties:
        alreadyHeld:
          type: boolean
          title: already_held
          description: already_held is true when the user already had a seat (idempotent no-op).
      title: AssignSeatResponse
      additionalProperties: false
    workspace.v1.BatchCheckItem:
      type: object
      properties:
        namespace:
          type: string
          title: namespace
        objectId:
          type: string
          title: object_id
        relation:
          type: string
          title: relation
        subjectUserId:
          type: string
          title: subject_user_id
      title: BatchCheckItem
      additionalProperties: false
      description: |-
        BatchCheck answers many Check questions in one round-trip — the list/grid hot
         path (filter N documents/lessons to those a user can see). Results are
         index-aligned to items; a per-item validation error is reported in that
         item's result rather than failing the whole batch. Every item shares the
         request's project_id and tenant_id.

         NOTE: BatchCheck carries NO request context, so CONDITIONAL grants (caveats:
         scope_in / not_after / ip_in_cidrs / consent_granted / age_at_least) cannot be
         evaluated through it and a conditioned grant is denied. Use Check (which takes
         `context`) for conditional grants.
    workspace.v1.BatchCheckRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        items:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.BatchCheckItem'
          title: items
        atLeastConsistencyToken:
          type: string
          title: at_least_consistency_token
          description: |-
            at_least_consistency_token, when set, demands the batch observe state at
             least as fresh as the token (from a prior WriteRelationTuples). Empty =
             read latest. A malformed or foreign-shard token is rejected.
      title: BatchCheckRequest
      additionalProperties: false
    workspace.v1.BatchCheckResponse:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.BatchCheckResult'
          title: results
      title: BatchCheckResponse
      additionalProperties: false
    workspace.v1.BatchCheckResult:
      type: object
      properties:
        allowed:
          type: boolean
          title: allowed
        error:
          type: string
          title: error
          description: |-
            error is non-empty when the item could not be evaluated (e.g. invalid
             arguments); allowed is then false.
      title: BatchCheckResult
      additionalProperties: false
    workspace.v1.CheckRequest:
      type: object
      properties:
        namespace:
          type: string
          title: namespace
        objectId:
          type: string
          title: object_id
        relation:
          type: string
          title: relation
        subjectUserId:
          type: string
          title: subject_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        subjectSet:
          title: subject_set
          description: |-
            subject_set asks whether a USERSET (e.g. group:cohort-7#member, or a
             service-account set) has the relation, rather than a concrete user. It is
             used iff subject_user_id is empty; exactly one of the two must be set. The
             answer is "does the queried userset intersect the relation's effective
             userset" — true if the set is structurally included, or if any concrete
             member of the set has the relation.
          $ref: '#/components/schemas/workspace.v1.SubjectSet'
        context:
          title: context
          description: |-
            context carries request-time attributes that CONDITIONAL grants are
             evaluated against (e.g. {"age": 9, "consent": true, "ip": "1.2.3.4",
             "now": "2026-06-16T00:00:00Z"}). Unset = no context; conditional grants
             then fail closed, while unconditional grants are unaffected.
          $ref: '#/components/schemas/google.protobuf.Struct'
        atLeastConsistencyToken:
          type: string
          title: at_least_consistency_token
          description: |-
            at_least_consistency_token, when set, demands the check observe state at
             least as fresh as the token (from a prior WriteRelationTuples). Empty =
             read latest. A malformed or foreign-shard token is rejected.
      title: CheckRequest
      additionalProperties: false
      description: |-
        Check answers "does subject have relation on namespace:object_id?",
         evaluating the namespace's userset rewrite rules transitively.
    workspace.v1.CheckResponse:
      type: object
      properties:
        allowed:
          type: boolean
          title: allowed
      title: CheckResponse
      additionalProperties: false
    workspace.v1.CreateGroupRequest:
      type: object
      properties:
        displayName:
          type: string
          title: display_name
        slug:
          type: string
          title: slug
        workspaceId:
          type: string
          title: workspace_id
          description: optional owning workspace; empty for a standalone group.
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: CreateGroupRequest
      additionalProperties: false
    workspace.v1.CreateGroupResponse:
      type: object
      properties:
        group:
          title: group
          $ref: '#/components/schemas/workspace.v1.Group'
      title: CreateGroupResponse
      additionalProperties: false
    workspace.v1.CreateInvitationRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        email:
          type: string
          title: email
        role:
          title: role
          $ref: '#/components/schemas/workspace.v1.Role'
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: CreateInvitationRequest
      additionalProperties: false
    workspace.v1.CreateInvitationResponse:
      type: object
      properties:
        invitation:
          title: invitation
          $ref: '#/components/schemas/workspace.v1.Invitation'
      title: CreateInvitationResponse
      additionalProperties: false
    workspace.v1.CreateProjectRequest:
      type: object
      properties:
        id:
          type: string
          title: id
        name:
          type: string
          title: name
        modelJson:
          type: string
          title: model_json
        dataRegion:
          type: string
          title: data_region
        maxCheckReads:
          type: integer
          title: max_check_reads
          format: int32
          description: |-
            max_check_reads: 0 leaves the project on the global default budget. A
             positive value overrides it and must be >= the same floor as the global
             (see GATEWAY_MAX_CHECK_READS).
      title: CreateProjectRequest
      additionalProperties: false
    workspace.v1.CreateProjectResponse:
      type: object
      properties:
        project:
          title: project
          $ref: '#/components/schemas/workspace.v1.Project'
      title: CreateProjectResponse
      additionalProperties: false
    workspace.v1.CreateWorkspaceRequest:
      type: object
      properties:
        displayName:
          type: string
          title: display_name
        slug:
          type: string
          title: slug
          description: slug is optional; the server derives one from display_name when empty.
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: CreateWorkspaceRequest
      additionalProperties: false
    workspace.v1.CreateWorkspaceResponse:
      type: object
      properties:
        workspace:
          title: workspace
          $ref: '#/components/schemas/workspace.v1.Workspace'
      title: CreateWorkspaceResponse
      additionalProperties: false
    workspace.v1.DeleteGroupRequest:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: DeleteGroupRequest
      additionalProperties: false
    workspace.v1.DeleteGroupResponse:
      type: object
      title: DeleteGroupResponse
      additionalProperties: false
    workspace.v1.DeleteWorkspaceRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: DeleteWorkspaceRequest
      additionalProperties: false
    workspace.v1.DeleteWorkspaceResponse:
      type: object
      title: DeleteWorkspaceResponse
      additionalProperties: false
    workspace.v1.DeprovisionUserRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
          description: |-
            tenant_id is accepted for call-shape symmetry but IGNORED: erasure spans
             every tenant of the project.
        userId:
          type: string
          title: user_id
      title: DeprovisionUserRequest
      additionalProperties: false
      description: |-
        DeprovisionUser revokes ALL of a user's ACCESS GRANTS: it deletes every
         relation tuple whose concrete subject is user_id, across all namespaces AND
         ALL TENANTS of the project, so a leaving user cannot retain live access in a
         sibling tenant; tenant_id is therefore ignored here. It does NOT delete the
         user's PII (membership/invitation/workspace rows) — full subject erasure and
         data-subject export are a separate concern (issue #14), so this RPC alone is
         not a GDPR/COPPA "right to erasure".
    workspace.v1.DeprovisionUserResponse:
      type: object
      properties:
        deletedCount:
          type:
            - integer
            - string
          title: deleted_count
          format: int64
      title: DeprovisionUserResponse
      additionalProperties: false
    workspace.v1.Enrollment:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        member:
          title: member
          $ref: '#/components/schemas/workspace.v1.GroupMember'
        state:
          title: state
          $ref: '#/components/schemas/workspace.v1.EnrollmentState'
        createdAt:
          title: created_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        updatedAt:
          title: updated_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
      title: Enrollment
      additionalProperties: false
    workspace.v1.ExpandRequest:
      type: object
      properties:
        namespace:
          type: string
          title: namespace
        objectId:
          type: string
          title: object_id
        relation:
          type: string
          title: relation
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        atLeastConsistencyToken:
          type: string
          title: at_least_consistency_token
          description: |-
            at_least_consistency_token, when set, demands the expansion reflect state at
             least as fresh as the token (from a prior WriteRelationTuples). Empty =
             read latest. A malformed or foreign-shard token is rejected.
      title: ExpandRequest
      additionalProperties: false
      description: Expand returns the effective userset tree for namespace:object_id#relation.
    workspace.v1.ExpandResponse:
      type: object
      properties:
        tree:
          title: tree
          $ref: '#/components/schemas/workspace.v1.UsersetTree'
      title: ExpandResponse
      additionalProperties: false
    workspace.v1.ExportSubjectGrantsRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        userId:
          type: string
          title: user_id
      title: ExportSubjectGrantsRequest
      additionalProperties: false
      description: |-
        ExportSubjectGrants returns every authorization grant a user holds in a
         project — across ALL tenants (mirroring DeprovisionUser's erase scope) — for
         GDPR/COPPA data-subject access requests. Read-only.
    workspace.v1.ExportSubjectGrantsResponse:
      type: object
      properties:
        grants:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.SubjectGrant'
          title: grants
      title: ExportSubjectGrantsResponse
      additionalProperties: false
    workspace.v1.GetGroupRequest:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: GetGroupRequest
      additionalProperties: false
    workspace.v1.GetGroupResponse:
      type: object
      properties:
        group:
          title: group
          $ref: '#/components/schemas/workspace.v1.Group'
      title: GetGroupResponse
      additionalProperties: false
    workspace.v1.GetProjectRequest:
      type: object
      properties:
        id:
          type: string
          title: id
      title: GetProjectRequest
      additionalProperties: false
    workspace.v1.GetProjectResponse:
      type: object
      properties:
        project:
          title: project
          $ref: '#/components/schemas/workspace.v1.Project'
      title: GetProjectResponse
      additionalProperties: false
    workspace.v1.GetSeatUsageRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        sku:
          type: string
          title: sku
      title: GetSeatUsageRequest
      additionalProperties: false
    workspace.v1.GetSeatUsageResponse:
      type: object
      properties:
        sku:
          type: string
          title: sku
        used:
          type: integer
          title: used
          format: int32
        limit:
          type: integer
          title: limit
          format: int32
        limited:
          type: boolean
          title: limited
          description: limited is false when no limit is configured for the sku (unlimited).
      title: GetSeatUsageResponse
      additionalProperties: false
    workspace.v1.GetWorkspaceRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: GetWorkspaceRequest
      additionalProperties: false
    workspace.v1.GetWorkspaceResponse:
      type: object
      properties:
        workspace:
          title: workspace
          $ref: '#/components/schemas/workspace.v1.Workspace'
      title: GetWorkspaceResponse
      additionalProperties: false
    workspace.v1.Group:
      type: object
      properties:
        id:
          type: string
          title: id
        projectId:
          type: string
          title: project_id
        workspaceId:
          type: string
          title: workspace_id
          description: |-
            workspace_id optionally scopes the group to a workspace (a company's
             internal groups). Empty for project-level / standalone groups (a B2C
             user's "family" list).
        slug:
          type: string
          title: slug
        displayName:
          type: string
          title: display_name
        createdByUserId:
          type: string
          title: created_by_user_id
        createdAt:
          title: created_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        updatedAt:
          title: updated_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        tenantId:
          type: string
          title: tenant_id
      title: Group
      additionalProperties: false
    workspace.v1.GroupMember:
      type: object
      oneOf:
        - properties:
            groupId:
              type: string
              title: group_id
          title: group_id
          required:
            - groupId
        - properties:
            userId:
              type: string
              title: user_id
          title: user_id
          required:
            - userId
      title: GroupMember
      additionalProperties: false
      description: GroupMember is either a user or a nested group.
    workspace.v1.Invitation:
      type: object
      properties:
        id:
          type: string
          title: id
        workspaceId:
          type: string
          title: workspace_id
        email:
          type: string
          title: email
        role:
          title: role
          $ref: '#/components/schemas/workspace.v1.Role'
        status:
          title: status
          $ref: '#/components/schemas/workspace.v1.InvitationStatus'
        invitedByUserId:
          type: string
          title: invited_by_user_id
        createdAt:
          title: created_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        expiresAt:
          title: expires_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        token:
          type: string
          title: token
          description: |-
            token is returned ONLY from CreateInvitation so the caller can deliver
             it out of band; it is never echoed by ListInvitations.
        tenantId:
          type: string
          title: tenant_id
      title: Invitation
      additionalProperties: false
    workspace.v1.ListEnrollmentsRequest:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: ListEnrollmentsRequest
      additionalProperties: false
    workspace.v1.ListEnrollmentsResponse:
      type: object
      properties:
        enrollments:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.Enrollment'
          title: enrollments
      title: ListEnrollmentsResponse
      additionalProperties: false
    workspace.v1.ListGroupMembersRequest:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: ListGroupMembersRequest
      additionalProperties: false
    workspace.v1.ListGroupMembersResponse:
      type: object
      properties:
        members:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.GroupMember'
          title: members
      title: ListGroupMembersResponse
      additionalProperties: false
    workspace.v1.ListGroupsRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
          description: 'optional: restrict to groups owned by this workspace.'
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: ListGroupsRequest
      additionalProperties: false
    workspace.v1.ListGroupsResponse:
      type: object
      properties:
        groups:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.Group'
          title: groups
      title: ListGroupsResponse
      additionalProperties: false
    workspace.v1.ListInvitationsRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: ListInvitationsRequest
      additionalProperties: false
    workspace.v1.ListInvitationsResponse:
      type: object
      properties:
        invitations:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.Invitation'
          title: invitations
      title: ListInvitationsResponse
      additionalProperties: false
    workspace.v1.ListMembersRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: ListMembersRequest
      additionalProperties: false
    workspace.v1.ListMembersResponse:
      type: object
      properties:
        members:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.Membership'
          title: members
      title: ListMembersResponse
      additionalProperties: false
    workspace.v1.ListObjectsRequest:
      type: object
      properties:
        namespace:
          type: string
          title: namespace
        relation:
          type: string
          title: relation
        subjectUserId:
          type: string
          title: subject_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        atLeastConsistencyToken:
          type: string
          title: at_least_consistency_token
          description: |-
            at_least_consistency_token, when set, demands the listing reflect state at
             least as fresh as the token (from a prior WriteRelationTuples). Empty =
             read latest. A malformed or foreign-shard token is rejected.
      title: ListObjectsRequest
      additionalProperties: false
      description: |-
        ListObjects returns the object_ids in a namespace where subject_user_id has
         the relation, for the given project/tenant. The reverse of Check: "which
         courses can this learner see?".
    workspace.v1.ListObjectsResponse:
      type: object
      properties:
        objectIds:
          type: array
          items:
            type: string
          title: object_ids
      title: ListObjectsResponse
      additionalProperties: false
    workspace.v1.ListProjectsRequest:
      type: object
      title: ListProjectsRequest
      additionalProperties: false
    workspace.v1.ListProjectsResponse:
      type: object
      properties:
        projects:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.Project'
          title: projects
      title: ListProjectsResponse
      additionalProperties: false
    workspace.v1.ListSeatsRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        sku:
          type: string
          title: sku
      title: ListSeatsRequest
      additionalProperties: false
    workspace.v1.ListSeatsResponse:
      type: object
      properties:
        seats:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.SeatAssignment'
          title: seats
      title: ListSeatsResponse
      additionalProperties: false
    workspace.v1.ListWorkspacesRequest:
      type: object
      properties:
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: ListWorkspacesRequest
      additionalProperties: false
      description: |-
        ListWorkspaces returns every workspace acting_user_id is an active member
         of, auto-provisioning their PERSONAL workspace on first call.
    workspace.v1.ListWorkspacesResponse:
      type: object
      properties:
        workspaces:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.Workspace'
          title: workspaces
      title: ListWorkspacesResponse
      additionalProperties: false
    workspace.v1.Membership:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        userId:
          type: string
          title: user_id
        role:
          title: role
          $ref: '#/components/schemas/workspace.v1.Role'
        status:
          title: status
          $ref: '#/components/schemas/workspace.v1.MembershipStatus'
        createdAt:
          title: created_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        updatedAt:
          title: updated_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        tenantId:
          type: string
          title: tenant_id
      title: Membership
      additionalProperties: false
    workspace.v1.Project:
      type: object
      properties:
        id:
          type: string
          title: id
        name:
          type: string
          title: name
        status:
          title: status
          $ref: '#/components/schemas/workspace.v1.ProjectStatus'
        modelJson:
          type: string
          title: model_json
          description: |-
            model_json is the project's authorization model as a JSON document. Empty
             means the project uses the built-in default model.
        createdAt:
          title: created_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        updatedAt:
          title: updated_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        dataRegion:
          type: string
          title: data_region
          description: |-
            data_region, when set, pins the project's data to a region/storage target.
             An instance configured for a different region refuses to serve it (fail
             closed). Empty means unpinned. Multi-region storage ROUTING is forward-
             compat; today this is recorded, validated, and enforced as a serving guard.
        maxCheckReads:
          type: integer
          title: max_check_reads
          format: int32
          description: |-
            max_check_reads, when > 0, overrides the global per-request read budget
             (GATEWAY_MAX_CHECK_READS) for this project's Check/Expand/ListObjects
             evaluations. 0 means unset: the project uses the fleet-wide default.
      title: Project
      additionalProperties: false
    workspace.v1.ReadRelationTuplesRequest:
      type: object
      properties:
        namespace:
          type: string
          title: namespace
        objectId:
          type: string
          title: object_id
        relation:
          type: string
          title: relation
        subjectUserId:
          type: string
          title: subject_user_id
          description: 'optional: restrict to tuples whose subject is this concrete user.'
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        atLeastConsistencyToken:
          type: string
          title: at_least_consistency_token
          description: |-
            at_least_consistency_token, when set, demands the read reflect state at
             least as fresh as the token (from a prior WriteRelationTuples). Empty =
             read latest. A malformed or foreign-shard token is rejected.
      title: ReadRelationTuplesRequest
      additionalProperties: false
      description: |-
        ReadRelationTuples returns stored tuples matching the non-empty filter
         fields (exact match). It does NOT evaluate userset rewrites — it is the
         raw store read. Use Check for permission decisions.
    workspace.v1.ReadRelationTuplesResponse:
      type: object
      properties:
        tuples:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.RelationTuple'
          title: tuples
      title: ReadRelationTuplesResponse
      additionalProperties: false
    workspace.v1.ReinstateMemberRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        userId:
          type: string
          title: user_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: ReinstateMemberRequest
      additionalProperties: false
    workspace.v1.ReinstateMemberResponse:
      type: object
      properties:
        membership:
          title: membership
          $ref: '#/components/schemas/workspace.v1.Membership'
      title: ReinstateMemberResponse
      additionalProperties: false
    workspace.v1.RelationTuple:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        namespace:
          type: string
          title: namespace
        objectId:
          type: string
          title: object_id
        relation:
          type: string
          title: relation
        subject:
          title: subject
          $ref: '#/components/schemas/workspace.v1.Subject'
        tenantId:
          type: string
          title: tenant_id
        expiresAt:
          title: expires_at
          description: |-
            expires_at, when set, makes the grant time-bounded: the tuple stops
             granting access once this instant passes (enforced at read time, so Check
             and tuple-to-userset walks both honor it). Unset = never expires. Expiry
             is mutable metadata, NOT part of tuple identity: re-writing the same tuple
             with a new expires_at updates it; deletes still match by identity.
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        conditionName:
          type: string
          title: condition_name
          description: |-
            condition_name, when set, makes the grant CONDITIONAL: it applies only if
             the named built-in condition (e.g. consent_granted, age_at_least,
             ip_in_cidrs, not_after) evaluates true against condition_params (bound
             here) and the request-time CheckRequest.context. Unset = unconditional.
             The condition is grant metadata, not part of tuple identity: re-writing the
             tuple replaces it; deletes still match by identity. Unknown name / missing
             input / ill-typed value all DENY (fail closed).
        conditionParams:
          title: condition_params
          description: |-
            condition_params are the static parameters bound to the condition at write
             time (e.g. {"min_age": 13} for age_at_least).
          $ref: '#/components/schemas/google.protobuf.Struct'
      title: RelationTuple
      additionalProperties: false
    workspace.v1.RemoveGroupMemberRequest:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        member:
          title: member
          $ref: '#/components/schemas/workspace.v1.GroupMember'
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: RemoveGroupMemberRequest
      additionalProperties: false
    workspace.v1.RemoveGroupMemberResponse:
      type: object
      title: RemoveGroupMemberResponse
      additionalProperties: false
    workspace.v1.RemoveMemberRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        userId:
          type: string
          title: user_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: RemoveMemberRequest
      additionalProperties: false
    workspace.v1.RemoveMemberResponse:
      type: object
      title: RemoveMemberResponse
      additionalProperties: false
    workspace.v1.RevokeInvitationRequest:
      type: object
      properties:
        invitationId:
          type: string
          title: invitation_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: RevokeInvitationRequest
      additionalProperties: false
    workspace.v1.RevokeInvitationResponse:
      type: object
      title: RevokeInvitationResponse
      additionalProperties: false
    workspace.v1.RevokeSeatRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        sku:
          type: string
          title: sku
        userId:
          type: string
          title: user_id
      title: RevokeSeatRequest
      additionalProperties: false
    workspace.v1.RevokeSeatResponse:
      type: object
      title: RevokeSeatResponse
      additionalProperties: false
    workspace.v1.SeatAssignment:
      type: object
      properties:
        sku:
          type: string
          title: sku
        userId:
          type: string
          title: user_id
        assignedAt:
          title: assigned_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
      title: SeatAssignment
      additionalProperties: false
    workspace.v1.SeatLimit:
      type: object
      properties:
        sku:
          type: string
          title: sku
        limit:
          type: integer
          title: limit
          format: int32
          description: |-
            limit is absent when the sku is unlimited (no cap configured); present
             (including 0, which admits no seats) when a cap is set.
          nullable: true
      title: SeatLimit
      additionalProperties: false
    workspace.v1.SetEnrollmentStateRequest:
      type: object
      properties:
        groupId:
          type: string
          title: group_id
        member:
          title: member
          $ref: '#/components/schemas/workspace.v1.GroupMember'
        state:
          title: state
          $ref: '#/components/schemas/workspace.v1.EnrollmentState'
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: SetEnrollmentStateRequest
      additionalProperties: false
    workspace.v1.SetEnrollmentStateResponse:
      type: object
      properties:
        enrollment:
          title: enrollment
          $ref: '#/components/schemas/workspace.v1.Enrollment'
      title: SetEnrollmentStateResponse
      additionalProperties: false
    workspace.v1.SetSeatLimitRequest:
      type: object
      properties:
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
        sku:
          type: string
          title: sku
        limit:
          type: integer
          title: limit
          format: int32
          description: |-
            limit, when present, sets the cap (must be >= 0; 0 admits no seats). When
             ABSENT, the sku's limit is CLEARED, returning it to unlimited — this is the
             only way to undo a previously-set cap.
          nullable: true
      title: SetSeatLimitRequest
      additionalProperties: false
    workspace.v1.SetSeatLimitResponse:
      type: object
      properties:
        limit:
          title: limit
          $ref: '#/components/schemas/workspace.v1.SeatLimit'
      title: SetSeatLimitResponse
      additionalProperties: false
    workspace.v1.Subject:
      type: object
      oneOf:
        - properties:
            set:
              title: set
              $ref: '#/components/schemas/workspace.v1.SubjectSet'
          title: set
          required:
            - set
        - properties:
            userId:
              type: string
              title: user_id
          title: user_id
          required:
            - userId
        - properties:
            wildcard:
              type: boolean
              title: wildcard
          title: wildcard
          required:
            - wildcard
      title: Subject
      additionalProperties: false
      description: |-
        Subject is the right-hand side of a tuple: a concrete user, a userset, or
         the public wildcard (matches every user — link-sharing / published
         content).
    workspace.v1.SubjectGrant:
      type: object
      properties:
        tenantId:
          type: string
          title: tenant_id
        namespace:
          type: string
          title: namespace
        objectId:
          type: string
          title: object_id
        relation:
          type: string
          title: relation
        viaGroup:
          type: string
          title: via_group
      title: SubjectGrant
      additionalProperties: false
      description: |-
        SubjectGrant is one access grant the subject holds. via_group is empty for a
         DIRECT grant (a tuple whose subject is the user, including the user's group
         memberships); otherwise it is the group id whose membership confers the grant.
    workspace.v1.SubjectSet:
      type: object
      properties:
        namespace:
          type: string
          title: namespace
        objectId:
          type: string
          title: object_id
        relation:
          type: string
          title: relation
      title: SubjectSet
      additionalProperties: false
      description: |-
        SubjectSet references the set of subjects related to another object — the
         userset `namespace:object_id#relation`. When relation is empty it denotes
         the object itself (rarely needed).
    workspace.v1.SuspendMemberRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        userId:
          type: string
          title: user_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: SuspendMemberRequest
      additionalProperties: false
      description: |-
        SuspendMember revokes a member's live access WITHOUT deleting their
         membership: it removes the backing role tuple (so every Check denies) and
         marks the membership suspended. ReinstateMember restores both. This is the
         safe way to pause access — denial is by tuple absence, not a status read on
         the hot path.
    workspace.v1.SuspendMemberResponse:
      type: object
      properties:
        membership:
          title: membership
          $ref: '#/components/schemas/workspace.v1.Membership'
      title: SuspendMemberResponse
      additionalProperties: false
    workspace.v1.TransferOwnershipRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        newOwnerUserId:
          type: string
          title: new_owner_user_id
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: TransferOwnershipRequest
      additionalProperties: false
      description: |-
        TransferOwnership hands a TEAM workspace's ownership to another user. Only
         the current owner (acting_user_id) may transfer; the new owner is granted the
         `owner` role (added as a member if needed) and the former owner is demoted to
         `admin` (kept, not orphaned). Personal workspaces cannot be transferred.
    workspace.v1.TransferOwnershipResponse:
      type: object
      properties:
        workspace:
          title: workspace
          $ref: '#/components/schemas/workspace.v1.Workspace'
      title: TransferOwnershipResponse
      additionalProperties: false
    workspace.v1.TupleUpdate:
      type: object
      properties:
        op:
          title: op
          $ref: '#/components/schemas/workspace.v1.TupleUpdate.Op'
        tuple:
          title: tuple
          $ref: '#/components/schemas/workspace.v1.RelationTuple'
      title: TupleUpdate
      additionalProperties: false
    workspace.v1.UpdateMemberRoleRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        userId:
          type: string
          title: user_id
        role:
          title: role
          $ref: '#/components/schemas/workspace.v1.Role'
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: UpdateMemberRoleRequest
      additionalProperties: false
    workspace.v1.UpdateMemberRoleResponse:
      type: object
      properties:
        membership:
          title: membership
          $ref: '#/components/schemas/workspace.v1.Membership'
      title: UpdateMemberRoleResponse
      additionalProperties: false
    workspace.v1.UpdateProjectRequest:
      type: object
      properties:
        id:
          type: string
          title: id
        name:
          type: string
          title: name
        status:
          title: status
          $ref: '#/components/schemas/workspace.v1.ProjectStatus'
        modelJson:
          type: string
          title: model_json
        dataRegion:
          type: string
          title: data_region
          description: |-
            data_region: empty leaves it unchanged (like name/model). Setting it
             repins the project; a repin converges across a horizontally-scaled fleet
             only after the resolver TTL (~30s), so it is not instantaneous.
        clearDataRegion:
          type: boolean
          title: clear_data_region
          description: |-
            clear_data_region, when true, reverts the project to region-agnostic
             (unpinned). Mutually exclusive with a non-empty data_region. This is the
             explicit "unpin" path, since an empty data_region means "leave unchanged".
        maxCheckReads:
          type: integer
          title: max_check_reads
          format: int32
          description: |-
            max_check_reads: 0 leaves it unchanged (like name/model). A positive value
             overrides the global per-request read budget and must be >= the same floor
             as the global default. The override converges across a horizontally-scaled
             fleet only after the resolver TTL (~30s), so it is not instantaneous.
        clearMaxCheckReads:
          type: boolean
          title: clear_max_check_reads
          description: |-
            clear_max_check_reads, when true, reverts the project to the global default
             budget. Mutually exclusive with a positive max_check_reads. This is the
             explicit "reset" path, since a 0 max_check_reads means "leave unchanged".
      title: UpdateProjectRequest
      additionalProperties: false
    workspace.v1.UpdateProjectResponse:
      type: object
      properties:
        project:
          title: project
          $ref: '#/components/schemas/workspace.v1.Project'
      title: UpdateProjectResponse
      additionalProperties: false
    workspace.v1.UpdateWorkspaceRequest:
      type: object
      properties:
        workspaceId:
          type: string
          title: workspace_id
        displayName:
          type: string
          title: display_name
        actingUserId:
          type: string
          title: acting_user_id
        projectId:
          type: string
          title: project_id
        tenantId:
          type: string
          title: tenant_id
      title: UpdateWorkspaceRequest
      additionalProperties: false
    workspace.v1.UpdateWorkspaceResponse:
      type: object
      properties:
        workspace:
          title: workspace
          $ref: '#/components/schemas/workspace.v1.Workspace'
      title: UpdateWorkspaceResponse
      additionalProperties: false
    workspace.v1.UsersetTree:
      type: object
      properties:
        type:
          title: type
          $ref: '#/components/schemas/workspace.v1.UsersetTree.NodeType'
        userIds:
          type: array
          items:
            type: string
          title: user_ids
          description: 'For LEAF nodes: the concrete subjects and usersets at this node.'
        sets:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.SubjectSet'
          title: sets
        children:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.UsersetTree'
          title: children
          description: |-
            For UNION / INTERSECTION nodes: the child subtrees. Empty for EXCLUSION
             (which uses `include` / `exclude` instead).
        expanded:
          title: expanded
          description: The userset this node expands.
          $ref: '#/components/schemas/workspace.v1.SubjectSet'
        wildcard:
          type: boolean
          title: wildcard
          description: 'For LEAF nodes: true if the public wildcard subject is present.'
        include:
          title: include
          description: |-
            For EXCLUSION nodes only: the include leg (the base set) and the exclude
             leg (subjects removed from it). The effective set is `include` minus
             `exclude`. Unset for every other node type.
          $ref: '#/components/schemas/workspace.v1.UsersetTree'
        exclude:
          title: exclude
          $ref: '#/components/schemas/workspace.v1.UsersetTree'
      title: UsersetTree
      additionalProperties: false
    workspace.v1.Workspace:
      type: object
      properties:
        id:
          type: string
          title: id
        projectId:
          type: string
          title: project_id
          description: project_id is the configuration/model shard (identity ADR-0002).
        slug:
          type: string
          title: slug
        displayName:
          type: string
          title: display_name
        type:
          title: type
          $ref: '#/components/schemas/workspace.v1.WorkspaceType'
        ownerUserId:
          type: string
          title: owner_user_id
        createdAt:
          title: created_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        updatedAt:
          title: updated_at
          $ref: '#/components/schemas/google.protobuf.Timestamp'
        tenantId:
          type: string
          title: tenant_id
          description: tenant_id is the data-isolation shard within the project; empty = default.
      title: Workspace
      additionalProperties: false
    workspace.v1.WriteRelationTuplesRequest:
      type: object
      properties:
        updates:
          type: array
          items:
            $ref: '#/components/schemas/workspace.v1.TupleUpdate'
          title: updates
        projectId:
          type: string
          title: project_id
          description: project_id scopes every tuple in the batch; empty = default project.
        tenantId:
          type: string
          title: tenant_id
      title: WriteRelationTuplesRequest
      additionalProperties: false
    workspace.v1.WriteRelationTuplesResponse:
      type: object
      properties:
        consistencyToken:
          type: string
          title: consistency_token
          description: |-
            consistency_token is an opaque "zookie" naming the shard's write sequence
             reached by this batch. Pass it to a later Check/Expand/ListObjects/
             BatchCheck/ReadRelationTuples as at_least_consistency_token to demand
             read-after-write (observe at least this write). Optional to use.
      title: WriteRelationTuplesResponse
      additionalProperties: false
    connect-protocol-version:
      type: number
      title: Connect-Protocol-Version
      enum:
        - 1
      description: Define the version of the Connect protocol
      const: 1
    connect-timeout-header:
      type: number
      title: Connect-Timeout-Ms
      description: Define the timeout, in ms
    connect.error:
      type: object
      properties:
        code:
          type: string
          examples:
            - not_found
          enum:
            - canceled
            - unknown
            - invalid_argument
            - deadline_exceeded
            - not_found
            - already_exists
            - permission_denied
            - resource_exhausted
            - failed_precondition
            - aborted
            - out_of_range
            - unimplemented
            - internal
            - unavailable
            - data_loss
            - unauthenticated
          description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
        message:
          type: string
          description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
        detail:
          $ref: '#/components/schemas/google.protobuf.Any'
      title: Connect Error
      additionalProperties: true
      description: 'Error type returned by Connect: https://connectrpc.com/docs/go/errors/#http-representation'
    google.protobuf.Any:
      type: object
      properties:
        type:
          type: string
        value:
          type: string
          format: binary
        debug:
          type: object
          additionalProperties: true
      additionalProperties: true
      description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message.
paths:
  /workspace.v1.WorkspaceService/CreateWorkspace:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: CreateWorkspace
      operationId: workspace.v1.WorkspaceService.CreateWorkspace
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.CreateWorkspaceRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.CreateWorkspaceResponse'
  /workspace.v1.WorkspaceService/GetWorkspace:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: GetWorkspace
      operationId: workspace.v1.WorkspaceService.GetWorkspace
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.GetWorkspaceRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.GetWorkspaceResponse'
  /workspace.v1.WorkspaceService/ListWorkspaces:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: ListWorkspaces
      operationId: workspace.v1.WorkspaceService.ListWorkspaces
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListWorkspacesRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListWorkspacesResponse'
  /workspace.v1.WorkspaceService/UpdateWorkspace:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: UpdateWorkspace
      operationId: workspace.v1.WorkspaceService.UpdateWorkspace
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.UpdateWorkspaceRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.UpdateWorkspaceResponse'
  /workspace.v1.WorkspaceService/TransferOwnership:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: TransferOwnership
      operationId: workspace.v1.WorkspaceService.TransferOwnership
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.TransferOwnershipRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.TransferOwnershipResponse'
  /workspace.v1.WorkspaceService/DeleteWorkspace:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: DeleteWorkspace
      operationId: workspace.v1.WorkspaceService.DeleteWorkspace
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.DeleteWorkspaceRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.DeleteWorkspaceResponse'
  /workspace.v1.WorkspaceService/AddMember:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: AddMember
      operationId: workspace.v1.WorkspaceService.AddMember
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.AddMemberRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.AddMemberResponse'
  /workspace.v1.WorkspaceService/UpdateMemberRole:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: UpdateMemberRole
      operationId: workspace.v1.WorkspaceService.UpdateMemberRole
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.UpdateMemberRoleRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.UpdateMemberRoleResponse'
  /workspace.v1.WorkspaceService/RemoveMember:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: RemoveMember
      operationId: workspace.v1.WorkspaceService.RemoveMember
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.RemoveMemberRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.RemoveMemberResponse'
  /workspace.v1.WorkspaceService/SuspendMember:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: SuspendMember
      operationId: workspace.v1.WorkspaceService.SuspendMember
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.SuspendMemberRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.SuspendMemberResponse'
  /workspace.v1.WorkspaceService/ReinstateMember:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: ReinstateMember
      operationId: workspace.v1.WorkspaceService.ReinstateMember
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ReinstateMemberRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ReinstateMemberResponse'
  /workspace.v1.WorkspaceService/ListMembers:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: ListMembers
      operationId: workspace.v1.WorkspaceService.ListMembers
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListMembersRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListMembersResponse'
  /workspace.v1.WorkspaceService/CreateInvitation:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: CreateInvitation
      operationId: workspace.v1.WorkspaceService.CreateInvitation
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.CreateInvitationRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.CreateInvitationResponse'
  /workspace.v1.WorkspaceService/AcceptInvitation:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: AcceptInvitation
      operationId: workspace.v1.WorkspaceService.AcceptInvitation
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.AcceptInvitationRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.AcceptInvitationResponse'
  /workspace.v1.WorkspaceService/ListInvitations:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: ListInvitations
      operationId: workspace.v1.WorkspaceService.ListInvitations
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListInvitationsRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListInvitationsResponse'
  /workspace.v1.WorkspaceService/RevokeInvitation:
    post:
      tags:
        - workspace.v1.WorkspaceService
      summary: RevokeInvitation
      operationId: workspace.v1.WorkspaceService.RevokeInvitation
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.RevokeInvitationRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.RevokeInvitationResponse'
  /workspace.v1.GroupService/CreateGroup:
    post:
      tags:
        - workspace.v1.GroupService
      summary: CreateGroup
      operationId: workspace.v1.GroupService.CreateGroup
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.CreateGroupRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.CreateGroupResponse'
  /workspace.v1.GroupService/GetGroup:
    post:
      tags:
        - workspace.v1.GroupService
      summary: GetGroup
      operationId: workspace.v1.GroupService.GetGroup
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.GetGroupRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.GetGroupResponse'
  /workspace.v1.GroupService/ListGroups:
    post:
      tags:
        - workspace.v1.GroupService
      summary: ListGroups
      operationId: workspace.v1.GroupService.ListGroups
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListGroupsRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListGroupsResponse'
  /workspace.v1.GroupService/DeleteGroup:
    post:
      tags:
        - workspace.v1.GroupService
      summary: DeleteGroup
      operationId: workspace.v1.GroupService.DeleteGroup
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.DeleteGroupRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.DeleteGroupResponse'
  /workspace.v1.GroupService/AddGroupMember:
    post:
      tags:
        - workspace.v1.GroupService
      summary: AddGroupMember
      operationId: workspace.v1.GroupService.AddGroupMember
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.AddGroupMemberRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.AddGroupMemberResponse'
  /workspace.v1.GroupService/RemoveGroupMember:
    post:
      tags:
        - workspace.v1.GroupService
      summary: RemoveGroupMember
      operationId: workspace.v1.GroupService.RemoveGroupMember
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.RemoveGroupMemberRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.RemoveGroupMemberResponse'
  /workspace.v1.GroupService/ListGroupMembers:
    post:
      tags:
        - workspace.v1.GroupService
      summary: ListGroupMembers
      operationId: workspace.v1.GroupService.ListGroupMembers
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListGroupMembersRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListGroupMembersResponse'
  /workspace.v1.GroupService/SetEnrollmentState:
    post:
      tags:
        - workspace.v1.GroupService
      summary: SetEnrollmentState
      description: |-
        SetEnrollmentState upserts a member's enrollment state and moves the
         backing `member` tuple accordingly (present iff the state grants access).
      operationId: workspace.v1.GroupService.SetEnrollmentState
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.SetEnrollmentStateRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.SetEnrollmentStateResponse'
  /workspace.v1.GroupService/ListEnrollments:
    post:
      tags:
        - workspace.v1.GroupService
      summary: ListEnrollments
      operationId: workspace.v1.GroupService.ListEnrollments
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListEnrollmentsRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListEnrollmentsResponse'
  /workspace.v1.AuthzService/WriteRelationTuples:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: WriteRelationTuples
      operationId: workspace.v1.AuthzService.WriteRelationTuples
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.WriteRelationTuplesRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.WriteRelationTuplesResponse'
  /workspace.v1.AuthzService/ReadRelationTuples:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: ReadRelationTuples
      operationId: workspace.v1.AuthzService.ReadRelationTuples
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ReadRelationTuplesRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ReadRelationTuplesResponse'
  /workspace.v1.AuthzService/Check:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: Check
      operationId: workspace.v1.AuthzService.Check
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.CheckRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.CheckResponse'
  /workspace.v1.AuthzService/BatchCheck:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: BatchCheck
      operationId: workspace.v1.AuthzService.BatchCheck
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.BatchCheckRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.BatchCheckResponse'
  /workspace.v1.AuthzService/Expand:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: Expand
      operationId: workspace.v1.AuthzService.Expand
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ExpandRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ExpandResponse'
  /workspace.v1.AuthzService/ListObjects:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: ListObjects
      operationId: workspace.v1.AuthzService.ListObjects
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListObjectsRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListObjectsResponse'
  /workspace.v1.AuthzService/DeprovisionUser:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: DeprovisionUser
      operationId: workspace.v1.AuthzService.DeprovisionUser
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.DeprovisionUserRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.DeprovisionUserResponse'
  /workspace.v1.AuthzService/ExportSubjectGrants:
    post:
      tags:
        - workspace.v1.AuthzService
      summary: ExportSubjectGrants
      operationId: workspace.v1.AuthzService.ExportSubjectGrants
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ExportSubjectGrantsRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ExportSubjectGrantsResponse'
  /workspace.v1.AdminService/CreateProject:
    post:
      tags:
        - workspace.v1.AdminService
      summary: CreateProject
      operationId: workspace.v1.AdminService.CreateProject
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.CreateProjectRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.CreateProjectResponse'
  /workspace.v1.AdminService/GetProject:
    post:
      tags:
        - workspace.v1.AdminService
      summary: GetProject
      operationId: workspace.v1.AdminService.GetProject
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.GetProjectRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.GetProjectResponse'
  /workspace.v1.AdminService/UpdateProject:
    post:
      tags:
        - workspace.v1.AdminService
      summary: UpdateProject
      operationId: workspace.v1.AdminService.UpdateProject
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.UpdateProjectRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.UpdateProjectResponse'
  /workspace.v1.AdminService/ListProjects:
    post:
      tags:
        - workspace.v1.AdminService
      summary: ListProjects
      operationId: workspace.v1.AdminService.ListProjects
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListProjectsRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListProjectsResponse'
  /workspace.v1.SeatService/SetSeatLimit:
    post:
      tags:
        - workspace.v1.SeatService
      summary: SetSeatLimit
      operationId: workspace.v1.SeatService.SetSeatLimit
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.SetSeatLimitRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.SetSeatLimitResponse'
  /workspace.v1.SeatService/GetSeatUsage:
    post:
      tags:
        - workspace.v1.SeatService
      summary: GetSeatUsage
      operationId: workspace.v1.SeatService.GetSeatUsage
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.GetSeatUsageRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.GetSeatUsageResponse'
  /workspace.v1.SeatService/AssignSeat:
    post:
      tags:
        - workspace.v1.SeatService
      summary: AssignSeat
      operationId: workspace.v1.SeatService.AssignSeat
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.AssignSeatRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.AssignSeatResponse'
  /workspace.v1.SeatService/RevokeSeat:
    post:
      tags:
        - workspace.v1.SeatService
      summary: RevokeSeat
      operationId: workspace.v1.SeatService.RevokeSeat
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.RevokeSeatRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.RevokeSeatResponse'
  /workspace.v1.SeatService/ListSeats:
    post:
      tags:
        - workspace.v1.SeatService
      summary: ListSeats
      operationId: workspace.v1.SeatService.ListSeats
      parameters:
        - name: Connect-Protocol-Version
          in: header
          required: true
          schema:
            $ref: '#/components/schemas/connect-protocol-version'
        - name: Connect-Timeout-Ms
          in: header
          schema:
            $ref: '#/components/schemas/connect-timeout-header'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workspace.v1.ListSeatsRequest'
        required: true
      responses:
        default:
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/connect.error'
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workspace.v1.ListSeatsResponse'
tags:
  - name: workspace.v1.WorkspaceService
  - name: workspace.v1.GroupService
  - name: workspace.v1.AuthzService
  - name: workspace.v1.AdminService
  - name: workspace.v1.SeatService
