openapi: 3.0.3
info:
  title: Gluo Portal API
  version: 2.3.1
  description: |
    Org governance API for Gluo portal customers — read and write.
    Authenticate with a Gluo-kind Motorical account API key (`ak_live_*`).
    All write requests (POST / PATCH / PUT / DELETE) require an
    `Idempotency-Key: <uuid-v4>` header.
servers:
  - url: https://motorical.com/api/gluo
    description: Production (ovh24 backend proxy to nc webmail-api)
security:
  - ApiKeyAuth: []
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'Use `Authorization: ApiKey ak_live_<prefix>_<secret>`'
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        format: uuid
      description: UUID v4. Same key + same body replays the cached response. Same key + different body → 422.
  schemas:
    Error:
      type: object
      properties:
        success:
          type: boolean
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
    PaginatedUsers:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/User'
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        status:
          type: string
          enum: [active, archived]
    UserCreate:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
          description: Legacy full-email form; prefer local_part + org_domain_id when provisioning on a verified domain.
        local_part:
          type: string
          description: Mailbox local part on org_domain_id (e.g. alice).
        org_domain_id:
          type: string
          format: uuid
          description: Verified org domain (wm_domains.id).
        first_name:
          type: string
        last_name:
          type: string
        display_name:
          type: string
        role:
          type: string
          enum: [member, org_admin, user_admin, group_admin, resource_admin, mail_admin, domain_admin, auditor]
          default: member
        show_in_address_book:
          type: boolean
        portal_access:
          type: boolean
        mailbox_plan:
          type: string
          enum: [none, new, attach]
          default: none
        mailbox_type:
          type: string
          enum: [simple, encrypted]
          default: simple
        attach_mailbox_id:
          type: string
          format: uuid
    UserPatch:
      type: object
      description: |
        Partial profile update. Omitted keys are unchanged (COALESCE semantics on nc).
        User archive/restore use POST /users/{id}/archive|restore — there is no status field on PATCH.
        Enabling portal_access on a user with no password may return 200 with initial_password instead of 204.
      properties:
        first_name:
          type: string
        last_name:
          type: string
        display_name:
          type: string
        role:
          type: string
          enum: [member, org_admin, user_admin, group_admin, resource_admin, mail_admin, domain_admin, auditor]
        show_in_address_book:
          type: boolean
        gal_group_ids:
          type: array
          items:
            type: string
            format: uuid
          description: Replaces GAL group restrictions when present; omitted leaves assignments unchanged.
        portal_access:
          type: boolean
    GroupUpdate:
      type: object
      description: PUT /groups/{id} — empty strings for name/email fields are ignored (NULLIF).
      properties:
        name:
          type: string
        display_name:
          type: string
        description:
          type: string
        local_part:
          type: string
        org_domain_id:
          type: string
          format: uuid
        primary_email:
          type: string
          format: email
        show_in_address_book:
          type: boolean
        gal_group_ids:
          type: array
          items:
            type: string
            format: uuid
    ResourceCreate:
      type: object
      required: [name]
      properties:
        resource_type:
          type: string
          enum: [room, equipment]
          default: room
        name:
          type: string
        description:
          type: string
        email:
          type: string
          format: email
          description: Legacy email; prefer local_part + org_domain_id.
        local_part:
          type: string
        org_domain_id:
          type: string
          format: uuid
        show_in_address_book:
          type: boolean
          default: true
        booking_policy:
          type: string
          enum: [auto_accept, auto_decline, approval_required]
          default: approval_required
        delegate_user_id:
          type: string
          format: uuid
    ResourcePatch:
      type: object
      description: PUT /resources/{id} — partial update; empty strings for scalar fields are ignored.
      properties:
        resource_type:
          type: string
          enum: [room, equipment]
        name:
          type: string
        description:
          type: string
        email:
          type: string
          format: email
        show_in_address_book:
          type: boolean
        gal_group_ids:
          type: array
          items:
            type: string
            format: uuid
        booking_policy:
          type: string
          enum: [auto_accept, auto_decline, approval_required]
        delegate_user_id:
          type: string
          format: uuid
          description: Empty string clears delegate.
    GroupMemberAdd:
      type: object
      required: [member_type, member_id]
      properties:
        member_type:
          type: string
          enum: [user, resource]
        member_id:
          type: string
          format: uuid
    DirectoryEntry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum: [user, group, resource]
        email:
          type: string
        display_name:
          type: string
        show_in_address_book:
          type: boolean
        gal_group_ids:
          type: array
          items:
            type: string
            format: uuid
        has_mailbox:
          type: boolean
    AddressBookVisibility:
      type: object
      required: [show_in_address_book]
      properties:
        show_in_address_book:
          type: boolean
    MailboxCreate:
      type: object
      required: [email_address]
      properties:
        email_address:
          type: string
          format: email
          description: Full address, e.g. `helpdesk@example.com`. Domain must exist in the org.
        mailbox_type:
          type: string
          enum: [simple, encrypted]
          default: simple
          description: Use `encrypted` for client-side S/MIME mailboxes (requires org encrypted IMAP enabled for the domain).
    MailboxTypeConvert:
      type: object
      required: [mailbox_type]
      properties:
        mailbox_type:
          type: string
          enum: [encrypted]
          description: One-way conversion from simple to encrypted IMAP only. Encrypted mailboxes cannot be converted back to simple.
    EncryptedImapConfig:
      type: object
      properties:
        enabled:
          type: boolean
        scope:
          type: string
          enum: [org, per_domain]
        domains:
          type: array
          items:
            type: object
    EncryptedImapConfigPatch:
      type: object
      properties:
        enabled:
          type: boolean
        scope:
          type: string
          enum: [org, per_domain]
        domain_settings:
          type: array
          items:
            type: object
            properties:
              domain_id:
                type: string
                format: uuid
              enabled:
                type: boolean
    EncryptedImapCertificateBundle:
      type: object
      required: [pem_key, pem_cert, password]
      properties:
        pem_key:
          type: string
        pem_cert:
          type: string
        password:
          type: string
          minLength: 6
        friendly_name:
          type: string
        mailbox_id:
          type: string
          format: uuid
        email:
          type: string
          format: email
    AuditEvent:
      type: object
      properties:
        id:
          type: string
        action:
          type: string
        entity_type:
          type: string
        entity_id:
          type: string
        created_at:
          type: string
          format: date-time
    BulkSubmitResponse:
      type: object
      properties:
        job_id:
          type: string
          format: uuid
        status_url:
          type: string
    BulkJob:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [queued, running, completed, failed]
        total_ops:
          type: integer
        processed_ops:
          type: integer
        failed_ops:
          type: integer
        results:
          type: array
          items:
            $ref: '#/components/schemas/BulkOpResult'
    BulkOpResult:
      type: object
      properties:
        op_index:
          type: integer
        status:
          type: string
          enum: [ok, error]
        entity_id:
          type: string
        code:
          type: string
          description: Error code when status=error
    BulkOperation:
      type: object
      required: [op]
      properties:
        op:
          type: string
          description: Operation name, e.g. `create`, `patch`, `archive`, `delete`
paths:
  /users:
    get:
      summary: List org users
      operationId: listUsers
      security:
        - ApiKeyAuth: [gluo.users.read]
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, archived, all]
            default: active
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Paginated user list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedUsers'
        '403':
          description: Missing scope or wrong key kind
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      summary: Create user
      operationId: createUser
      security:
        - ApiKeyAuth: [gluo.users.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '201':
          description: Created user
        '400':
          description: Validation error or missing Idempotency-Key
        '422':
          description: Idempotency-Key body mismatch
  /users/{userId}:
    patch:
      summary: Update user (users use PATCH; not PUT)
      operationId: patchUser
      security:
        - ApiKeyAuth: [gluo.users.write]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserPatch'
      responses:
        '200':
          description: Updated user
        '404':
          description: User not found
    delete:
      summary: Delete (purge) user
      operationId: deleteUser
      security:
        - ApiKeyAuth: [gluo.users.write]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Deleted
        '404':
          description: User not found
  /users/{userId}/archive:
    post:
      summary: Archive user (soft-delete)
      operationId: archiveUser
      security:
        - ApiKeyAuth: [gluo.users.write]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: User archived
  /users/{userId}/restore:
    post:
      summary: Restore archived user
      operationId: restoreUser
      security:
        - ApiKeyAuth: [gluo.users.write]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: User restored
  /users/{userId}/mailbox:
    post:
      summary: Provision mailbox for user
      operationId: provisionUserMailbox
      security:
        - ApiKeyAuth: [gluo.users.write]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                mailbox_type:
                  type: string
                  enum: [simple, encrypted]
                  default: simple
      responses:
        '201':
          description: Mailbox provisioned
  /users/{userId}/portal-password/rotate:
    post:
      summary: Rotate portal password (one-time secret in response)
      operationId: rotateUserPortalPassword
      security:
        - ApiKeyAuth: [gluo.users.write]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: New portal password (shown once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  portal_password:
                    type: string
                    description: One-time plaintext password — store immediately; not retrievable again
  /groups:
    get:
      summary: List groups
      operationId: listGroups
      security:
        - ApiKeyAuth: [gluo.groups.read]
      responses:
        '200':
          description: Group list
    post:
      summary: Create group
      operationId: createGroup
      security:
        - ApiKeyAuth: [gluo.groups.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                email:
                  type: string
                  format: email
      responses:
        '201':
          description: Created group
  /groups/{groupId}:
    put:
      summary: Update group (groups use PUT; not PATCH)
      operationId: updateGroup
      security:
        - ApiKeyAuth: [gluo.groups.write]
      parameters:
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupUpdate'
      responses:
        '200':
          description: Updated group
    delete:
      summary: Delete group
      operationId: deleteGroup
      security:
        - ApiKeyAuth: [gluo.groups.write]
      parameters:
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Deleted
  /groups/members/bulk:
    get:
      summary: Bulk group membership lookup
      operationId: listGroupMembersBulk
      security:
        - ApiKeyAuth: [gluo.directory.read]
      responses:
        '200':
          description: Bulk membership data
  /groups/{groupId}/members:
    get:
      summary: List group members
      operationId: listGroupMembers
      security:
        - ApiKeyAuth: [gluo.directory.read]
      parameters:
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Group member list
    post:
      summary: Add member to group
      operationId: addGroupMember
      security:
        - ApiKeyAuth: [gluo.groups.write]
      parameters:
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupMemberAdd'
      responses:
        '201':
          description: Member added
    delete:
      summary: Remove member from group (DELETE with JSON body — there is no path-param form)
      operationId: removeGroupMember
      security:
        - ApiKeyAuth: [gluo.groups.write]
      parameters:
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        description: Identify the member to remove by type + id.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupMemberAdd'
      responses:
        '204':
          description: Member removed
  /groups/{groupId}/address-book-visibility:
    put:
      summary: Set group GAL / directory visibility
      operationId: setGroupAddressBookVisibility
      security:
        - ApiKeyAuth: [gluo.groups.write]
      parameters:
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddressBookVisibility'
      responses:
        '200':
          description: Visibility updated
  /resources:
    get:
      summary: List bookable resources
      operationId: listResources
      security:
        - ApiKeyAuth: [gluo.resources.read]
      responses:
        '200':
          description: Resource list
    post:
      summary: Create resource
      operationId: createResource
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResourceCreate'
      responses:
        '201':
          description: Created resource
  /resources/{resourceId}:
    get:
      summary: Get one resource
      operationId: getResource
      security:
        - ApiKeyAuth: [gluo.resources.read]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Resource record
    put:
      summary: Update resource (resources use PUT; not PATCH)
      operationId: updateResource
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResourcePatch'
      responses:
        '200':
          description: Updated resource
    delete:
      summary: Delete resource
      operationId: deleteResource
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Deleted
  /resources/{resourceId}/address-book-visibility:
    put:
      summary: Set resource GAL / directory visibility
      operationId: setResourceAddressBookVisibility
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddressBookVisibility'
      responses:
        '200':
          description: Visibility updated
  /resources/{resourceId}/aliases:
    get:
      summary: List resource email aliases
      operationId: listResourceAliases
      security:
        - ApiKeyAuth: [gluo.resources.read]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Alias list
    post:
      summary: Add resource email alias
      operationId: createResourceAlias
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [alias_email]
              properties:
                alias_email:
                  type: string
                  format: email
      responses:
        '201':
          description: Alias created
  /resources/{resourceId}/aliases/{aliasId}:
    delete:
      summary: Delete resource email alias
      operationId: deleteResourceAlias
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: aliasId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Alias deleted
  /resources/{resourceId}/tags:
    post:
      summary: Add tag to resource
      operationId: createResourceTag
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tag]
              properties:
                tag:
                  type: string
      responses:
        '201':
          description: Tag added
  /resources/{resourceId}/tags/{tag}:
    delete:
      summary: Remove tag from resource
      operationId: deleteResourceTag
      security:
        - ApiKeyAuth: [gluo.resources.write]
      parameters:
        - name: resourceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: tag
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Tag removed
  /mailboxes:
    get:
      summary: List mailboxes
      operationId: listMailboxes
      security:
        - ApiKeyAuth: [gluo.mailboxes.read]
      responses:
        '200':
          description: Mailbox list
    post:
      summary: Create shared/unowned mailbox
      operationId: createMailbox
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MailboxCreate'
      responses:
        '201':
          description: Created mailbox
  # Note: there is no generic mailbox update — specific changes are sub-routes
  # (credentials, aliases, primary-email, mailbox-type, encrypted-imap sub-ops).
  /mailboxes/{mailboxId}/credentials:
    get:
      summary: List mailbox SMTP/IMAP credentials
      operationId: listMailboxCredentials
      security:
        - ApiKeyAuth: [gluo.mailboxes.read]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Credential list
    post:
      summary: Create a mailbox credential
      operationId: createMailboxCredential
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
      responses:
        '201':
          description: Credential created
  /mailboxes/{mailboxId}/credentials/primary/rotate:
    post:
      summary: Rotate primary mailbox credential (one-time secret in response)
      operationId: rotatePrimaryMailboxCredential
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: New credential secret (shown once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  password:
                    type: string
                    description: One-time SMTP/IMAP password — store immediately
  /mailboxes/{mailboxId}/credentials/{credentialId}:
    delete:
      summary: Delete a mailbox credential
      operationId: deleteMailboxCredential
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: credentialId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Credential deleted
  /mailboxes/{mailboxId}/aliases:
    get:
      summary: List mailbox aliases
      operationId: listMailboxAliases
      security:
        - ApiKeyAuth: [gluo.mailboxes.read]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Alias list
    post:
      summary: Add mailbox alias
      operationId: createMailboxAlias
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [alias_email]
              properties:
                alias_email:
                  type: string
                  format: email
      responses:
        '201':
          description: Alias created
  /mailboxes/{mailboxId}/aliases/{aliasId}:
    delete:
      summary: Delete mailbox alias
      operationId: deleteMailboxAlias
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: aliasId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Alias deleted
  /mailboxes/{mailboxId}/primary-email:
    put:
      summary: Change mailbox primary email address
      operationId: setMailboxPrimaryEmail
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email_address]
              properties:
                email_address:
                  type: string
                  format: email
      responses:
        '200':
          description: Primary email updated
  /mailboxes/{mailboxId}/encrypted-imap/header-visibility:
    get:
      summary: Read encrypted mailbox header visibility mode
      operationId: getEncryptedImapHeaderVisibility
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.read]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Header visibility settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  headers_visible:
                    type: boolean
                    description: When false, encrypted messages may hide headers on supporting clients
    patch:
      summary: Update encrypted mailbox header visibility mode
      operationId: patchEncryptedImapHeaderVisibility
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [headers_visible]
              properties:
                headers_visible:
                  type: boolean
      responses:
        '200':
          description: Updated header visibility
  /mailboxes/{mailboxId}/encrypted-imap/certificate-reissue:
    post:
      summary: Reissue encrypted IMAP certificate for mailbox
      operationId: reissueEncryptedImapCertificate
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Certificate reissued
  /mailboxes/{mailboxId}:
    delete:
      summary: Delete mailbox
      operationId: deleteMailbox
      security:
        - ApiKeyAuth: [gluo.mailboxes.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Deleted
  /mailboxes/{mailboxId}/mailbox-type:
    post:
      summary: Convert simple mailbox to encrypted IMAP (one-way)
      operationId: convertMailboxType
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.write]
      parameters:
        - name: mailboxId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MailboxTypeConvert'
      responses:
        '200':
          description: Converted mailbox
  /org/address-books:
    get:
      summary: List shared address books
      operationId: listAddressBooks
      security:
        - ApiKeyAuth: [gluo.address_books.read]
      responses:
        '200':
          description: Address book list
    post:
      summary: Create shared address book
      operationId: createAddressBook
      security:
        - ApiKeyAuth: [gluo.address_books.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                group_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Groups granted access to this address book (group-access grant mechanism)
      responses:
        '201':
          description: Address book created
  /org/address-books/{bookId}:
    put:
      summary: Update shared address book
      operationId: updateAddressBook
      security:
        - ApiKeyAuth: [gluo.address_books.write]
      parameters:
        - name: bookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                group_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
      responses:
        '200':
          description: Address book updated
    delete:
      summary: Delete shared address book
      operationId: deleteAddressBook
      security:
        - ApiKeyAuth: [gluo.address_books.write]
      parameters:
        - name: bookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Address book deleted
  /org:
    get:
      summary: Read org profile and settings summary
      operationId: getOrg
      security:
        - ApiKeyAuth: [gluo.org.read]
      responses:
        '200':
          description: Org record
  /org/membership-config:
    get:
      summary: Read org membership configuration
      operationId: getOrgMembershipConfig
      security:
        - ApiKeyAuth: [gluo.org.read]
      responses:
        '200':
          description: Membership config
    patch:
      summary: Update org membership configuration
      operationId: patchOrgMembershipConfig
      security:
        - ApiKeyAuth: [gluo.org.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled_admin_roles]
              properties:
                enabled_admin_roles:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Membership config updated
  /org/directory:
    get:
      summary: Org directory hub (users, groups, resources with GAL metadata)
      operationId: getOrgDirectory
      security:
        - ApiKeyAuth: [gluo.directory.read]
      responses:
        '200':
          description: Directory entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/DirectoryEntry'
                  total:
                    type: integer
  /org/encrypted-imap-config:
    get:
      summary: Read encrypted IMAP org settings
      operationId: getEncryptedImapConfig
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.read]
      responses:
        '200':
          description: Encrypted IMAP configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EncryptedImapConfig'
    patch:
      summary: Update encrypted IMAP org settings
      operationId: patchEncryptedImapConfig
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EncryptedImapConfigPatch'
      responses:
        '200':
          description: Updated configuration
  /encrypted-imap/certificates:
    get:
      summary: List org encrypted IMAP certificates
      operationId: listEncryptedImapCertificates
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.read]
      responses:
        '200':
          description: Certificate list
  /encrypted-imap/certificate-bundle:
    post:
      summary: Download PKCS#12 certificate bundle ZIP
      operationId: downloadEncryptedImapCertificateBundle
      security:
        - ApiKeyAuth: [gluo.encrypted_imap.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EncryptedImapCertificateBundle'
      responses:
        '200':
          description: ZIP stream
          content:
            application/zip:
              schema:
                type: string
                format: binary
  /org/domains:
    get:
      summary: List org domains
      operationId: listDomains
      security:
        - ApiKeyAuth: [gluo.domains.read]
      responses:
        '200':
          description: Domain list
    post:
      summary: Add domain to org
      operationId: createDomain
      security:
        - ApiKeyAuth: [gluo.domains.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain:
                  type: string
      responses:
        '201':
          description: Domain added
  # Note: there is no generic domain update — specific changes are sub-routes
  # (catchall, verify, transfer, dns-records, simple-status, delete-check).
  /org/domains/{domainId}/dns-records:
    get:
      summary: DNS records required for domain verification
      operationId: getDomainDnsRecords
      security:
        - ApiKeyAuth: [gluo.domains.read]
      parameters:
        - name: domainId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: DNS record set
  /org/domains/{domainId}/simple-status:
    get:
      summary: Simplified domain verification status
      operationId: getDomainSimpleStatus
      security:
        - ApiKeyAuth: [gluo.domains.read]
      parameters:
        - name: domainId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Simple status payload
  /org/domains/{domainId}/delete-check:
    get:
      summary: Pre-delete safety check for domain
      operationId: getDomainDeleteCheck
      security:
        - ApiKeyAuth: [gluo.domains.read]
      parameters:
        - name: domainId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Delete check result
  /org/domains/{domainId}/verify:
    post:
      summary: Trigger domain DNS verification
      operationId: verifyDomain
      security:
        - ApiKeyAuth: [gluo.domains.write]
      parameters:
        - name: domainId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Verification attempted
  /org/domains/{domainId}/transfer:
    post:
      summary: Transfer domain to another org
      operationId: transferDomain
      security:
        - ApiKeyAuth: [gluo.domains.write]
      parameters:
        - name: domainId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [target_org_id]
              properties:
                target_org_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Domain transferred
  /org/domains/{domainId}/catchall:
    put:
      summary: Configure domain catch-all routing
      operationId: setDomainCatchall
      security:
        - ApiKeyAuth: [gluo.domains.write]
      parameters:
        - name: domainId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled:
                  type: boolean
                target:
                  type: string
                  format: email
                  description: Catch-all destination when enabled
      responses:
        '200':
          description: Catch-all updated
  /org/domains/{domainId}:
    delete:
      summary: Remove domain from org
      operationId: deleteDomain
      security:
        - ApiKeyAuth: [gluo.domains.write]
      parameters:
        - name: domainId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Deleted
  /audit/events:
    get:
      summary: List audit events
      operationId: listAuditEvents
      security:
        - ApiKeyAuth: [gluo.audit.read]
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Paginated audit events
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuditEvent'
  /audit/events/export/subject:
    get:
      summary: Export audit events by subject (CSV)
      operationId: exportAuditEventsBySubject
      security:
        - ApiKeyAuth: [gluo.audit.read]
      responses:
        '200':
          description: CSV export filtered by subject
          content:
            text/csv:
              schema:
                type: string
  /audit/events/export:
    get:
      summary: Export audit events (CSV)
      operationId: exportAuditEvents
      security:
        - ApiKeyAuth: [gluo.audit.read]
      responses:
        '200':
          description: CSV export
          content:
            text/csv:
              schema:
                type: string
  /bulk/users:
    post:
      summary: Bulk user operations (async)
      operationId: bulkUsers
      security:
        - ApiKeyAuth: [gluo.bulk.write, gluo.users.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operations]
              properties:
                operations:
                  type: array
                  maxItems: 500
                  items:
                    $ref: '#/components/schemas/BulkOperation'
      responses:
        '202':
          description: Job queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkSubmitResponse'
  /bulk/groups:
    post:
      summary: Bulk group operations (async)
      operationId: bulkGroups
      security:
        - ApiKeyAuth: [gluo.bulk.write, gluo.groups.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operations]
              properties:
                operations:
                  type: array
                  maxItems: 500
                  items:
                    $ref: '#/components/schemas/BulkOperation'
      responses:
        '202':
          description: Job queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkSubmitResponse'
  /bulk/resources:
    post:
      summary: Bulk resource operations (async)
      operationId: bulkResources
      security:
        - ApiKeyAuth: [gluo.bulk.write, gluo.resources.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operations]
              properties:
                operations:
                  type: array
                  maxItems: 500
                  items:
                    $ref: '#/components/schemas/BulkOperation'
      responses:
        '202':
          description: Job queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkSubmitResponse'
  /bulk/mailboxes:
    post:
      summary: Bulk mailbox operations (async)
      operationId: bulkMailboxes
      security:
        - ApiKeyAuth: [gluo.bulk.write, gluo.mailboxes.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operations]
              properties:
                operations:
                  type: array
                  maxItems: 500
                  items:
                    $ref: '#/components/schemas/BulkOperation'
      responses:
        '202':
          description: Job queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkSubmitResponse'
  /bulk/domains:
    post:
      summary: Bulk domain operations (async)
      operationId: bulkDomains
      security:
        - ApiKeyAuth: [gluo.bulk.write, gluo.domains.write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operations]
              properties:
                operations:
                  type: array
                  maxItems: 500
                  items:
                    $ref: '#/components/schemas/BulkOperation'
      responses:
        '202':
          description: Job queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkSubmitResponse'
  /bulk/jobs/{jobId}:
    get:
      summary: Get bulk job status
      operationId: getBulkJob
      security:
        - ApiKeyAuth: [gluo.bulk.write]
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Job status + per-op results (populated when completed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkJob'
        '404':
          description: Job not found or expired (7-day retention)
