openapi: 3.1.0
info:
  title: Central API
  version: 1.0.0
  description: 'AcuGIS Central portal HTTP API.


    Paths are shown without an optional BASE_PATH prefix. When BASE_PATH is set at process start, all
    routes are mounted under that prefix. Reverse proxies commonly expose Central at the site root on
    port 443 while the process listens on :8888.


    Authentication is optional for some routes and required for others. Supported credentials:

    - Cookie acugis_session

    - Authorization: Bearer agp_... personal access token


    JWT and OAuth are not implemented. /api/maps|/reports|/forms|/datasets are stubs.


    Admin identity/ACL routes are registered twice: under /api/admin/* and under /admin/api/* aliases.

    '
servers:
- url: http://127.0.0.1:8888
  description: Direct Central listener (default)
- url: /
  description: Typical reverse-proxy site root
tags:
- name: System
- name: Identity
- name: Sessions
- name: Catalog
- name: Portal
- name: Portal Admin
- name: Stubs
- name: Admin Users
- name: Admin Groups
- name: Admin ACL
- name: Admin UI Aliases
paths:
  /api/health:
    get:
      summary: Liveness probe
      tags:
      - System
      responses:
        '200':
          description: Service is running
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
              example:
                status: ok
      security: []
  /api/me:
    get:
      summary: Current authentication state
      tags:
      - Identity
      responses:
        '200':
          description: Authentication status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeResponse'
        '500':
          description: Session/token lookup failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Optional auth. Returns authenticated:false when anonymous or when the auth database
        is not configured.
      security:
      - {}
      - cookieAuth: []
      - bearerAuth: []
  /api/me/tokens:
    get:
      summary: List personal access tokens
      tags:
      - Identity
      responses:
        '200':
          description: Non-revoked tokens
          content:
            application/json:
              schema:
                type: object
                required:
                - tokens
                properties:
                  tokens:
                    type: array
                    items:
                      $ref: '#/components/schemas/PersonalAccessToken'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: PAT store unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
    post:
      summary: Create personal access token
      tags:
      - Identity
      responses:
        '201':
          description: Created token metadata and one-time secret
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedPATResponse'
        '400':
          description: Invalid payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: PAT store unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePATInput'
            example:
              name: ci
              expires_at: 2030-01-01
  /api/me/tokens/{id}:
    delete:
      summary: Revoke personal access token
      tags:
      - Identity
      responses:
        '204':
          description: Revoked
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Token not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: PAT store unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
  /api/maps:
    get:
      summary: Stub empty collection
      tags:
      - Stubs
      responses:
        '200':
          description: Empty placeholder collection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StubCollection'
              example:
                items: []
                total: 0
                note: not yet wired to qgis-publish-service
      security: []
  /api/reports:
    get:
      summary: Stub empty collection
      tags:
      - Stubs
      responses:
        '200':
          description: Empty placeholder collection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StubCollection'
              example:
                items: []
                total: 0
                note: not yet wired to Jasper reports
      security: []
  /api/forms:
    get:
      summary: Stub empty collection
      tags:
      - Stubs
      responses:
        '200':
          description: Empty placeholder collection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StubCollection'
              example:
                items: []
                total: 0
                note: not yet wired to GeoSync forms
      security: []
  /api/datasets:
    get:
      summary: Stub empty collection
      tags:
      - Stubs
      responses:
        '200':
          description: Empty placeholder collection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StubCollection'
              example:
                items: []
                total: 0
                note: not yet wired to GeoVault datasets
      security: []
  /api/catalog:
    get:
      summary: Aggregate federation catalog
      tags:
      - Catalog
      responses:
        '200':
          description: Catalog buckets and optional warnings
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CatalogResponse'
        '503':
          description: Catalog aggregator unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Forwards the current session cookie to same-origin discovery endpoints. Downstream
        failures become warnings.
      security:
      - {}
      - cookieAuth: []
      - bearerAuth: []
  /api/portal/cards:
    get:
      summary: List visible portal cards
      tags:
      - Portal
      responses:
        '200':
          description: Visible cards
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalCollection'
        '500':
          description: Query failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - {}
      - cookieAuth: []
      - bearerAuth: []
  /api/admin/portal/services:
    get:
      summary: List registered discovery services
      tags:
      - Portal Admin
      responses:
        '200':
          description: Static service registry
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Service'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Auth DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
  /api/admin/portal/services/{service_key}/resources:
    get:
      summary: Discover resources for a service
      tags:
      - Portal Admin
      responses:
        '200':
          description: Discovery result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DiscoveryResult'
        '404':
          description: Unknown service
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: Downstream discovery failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - name: service_key
        in: path
        required: true
        schema:
          type: string
  /api/admin/portal/cards:
    get:
      summary: List all portal cards
      tags:
      - Portal Admin
      responses:
        '200':
          description: All cards
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalCollection'
        '503':
          description: Portal DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Query failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
    post:
      summary: Create portal card
      tags:
      - Portal Admin
      responses:
        '201':
          description: Created card
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Portal DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Insert failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardInput'
  /api/admin/portal/cards/{id}:
    put:
      summary: Replace portal card
      tags:
      - Portal Admin
      responses:
        '200':
          description: Updated card
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
        '400':
          description: Invalid id/body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Portal DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardInput'
    delete:
      summary: Delete portal card
      tags:
      - Portal Admin
      responses:
        '204':
          description: Deleted
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Portal DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
  /api/admin/portal/cards/reorder:
    post:
      summary: Reorder portal cards
      tags:
      - Portal Admin
      responses:
        '200':
          description: Reorder applied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusOK'
        '400':
          description: Invalid reorder payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Portal DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/ReorderItem'
  /api/admin/portal/thumbnails:
    post:
      summary: Upload portal thumbnail
      tags:
      - Portal Admin
      responses:
        '201':
          description: Stored thumbnail URL
          content:
            application/json:
              schema:
                type: object
                required:
                - url
                properties:
                  url:
                    type: string
              example:
                url: /thumbnails/abcdef0123456789.png
        '400':
          description: Invalid upload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Max 5 MiB. JPEG, PNG, WebP, or GIF.
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - file
              properties:
                file:
                  type: string
                  format: binary
  /api/admin/users:
    get:
      summary: List users
      tags:
      - Admin Users
      responses:
        '200':
          description: Users
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuthUser'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Auth DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - name: search
        in: query
        required: false
        schema:
          type: string
    post:
      summary: Create user
      tags:
      - Admin Users
      responses:
        '201':
          description: Created user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthUserInput'
  /api/admin/users/{id}:
    get:
      summary: Get user
      tags:
      - Admin Users
      responses:
        '200':
          description: User
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
    put:
      summary: Update user
      tags:
      - Admin Users
      responses:
        '200':
          description: Updated user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          description: Invalid payload/id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Applies email, enabled, and group_ids. username/password in body are ignored.
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthUserInput'
  /api/admin/users/{id}/password:
    post:
      summary: Reset user password
      tags:
      - Admin Users
      responses:
        '200':
          description: Password updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusOK'
        '400':
          description: Invalid payload/id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - password
              properties:
                password:
                  type: string
                  minLength: 1
              additionalProperties: false
  /api/admin/groups:
    get:
      summary: List groups
      tags:
      - Admin Groups
      responses:
        '200':
          description: Groups
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Group'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
    post:
      summary: Create group
      tags:
      - Admin Groups
      responses:
        '201':
          description: Created group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Group'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupInput'
  /api/admin/groups/{id}:
    put:
      summary: Update group
      tags:
      - Admin Groups
      responses:
        '200':
          description: Updated group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Group'
        '400':
          description: Validation/protected group error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupInput'
    delete:
      summary: Delete group
      tags:
      - Admin Groups
      responses:
        '204':
          description: Deleted
        '400':
          description: Protected group or invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
  /api/admin/groups/{id}/users:
    post:
      summary: Add user to group
      tags:
      - Admin Groups
      responses:
        '200':
          description: Membership ensured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusOK'
        '400':
          description: Invalid id/body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user_id
              properties:
                user_id:
                  type: integer
                  minimum: 1
              additionalProperties: false
  /api/admin/groups/{id}/users/{user_id}:
    delete:
      summary: Remove user from group
      tags:
      - Admin Groups
      responses:
        '204':
          description: Removed (also when membership already absent)
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      - name: user_id
        in: path
        required: true
        schema:
          type: integer
          minimum: 1
  /api/admin/resources:
    get:
      summary: List resources
      tags:
      - Admin ACL
      responses:
        '200':
          description: Resources
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Resource'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - name: service
        in: query
        schema:
          type: string
      - name: resource_type
        in: query
        schema:
          type: string
  /api/admin/resource-access-overview:
    get:
      summary: Resource access overview
      tags:
      - Admin ACL
      responses:
        '200':
          description: Overview rows
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/ResourceAccessOverview'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - name: search
        in: query
        schema:
          type: string
      - name: service
        in: query
        schema:
          type: string
      - name: resource_type
        in: query
        schema:
          type: string
      - name: visibility
        in: query
        schema:
          type: string
          enum:
          - public
          - restricted
  /api/admin/resources/{id}/permissions:
    get:
      summary: Get resource ACL matrix
      tags:
      - Admin ACL
      responses:
        '200':
          description: ACL detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResourceACLDetail'
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
    put:
      summary: Replace resource ACL matrix
      tags:
      - Admin ACL
      responses:
        '200':
          description: ACL replaced
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusOK'
        '400':
          description: Invalid visibility/body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReplaceResourceACLInput'
  /api/admin/permissions:
    get:
      summary: List permission rows
      tags:
      - Admin ACL
      responses:
        '200':
          description: Permissions
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Permission'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
    post:
      summary: Grant permission row
      tags:
      - Admin ACL
      responses:
        '201':
          description: Granted permission
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Permission'
        '400':
          description: Invalid permission/body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PermissionInput'
  /api/admin/permissions/{id}:
    delete:
      summary: Revoke permission row
      tags:
      - Admin ACL
      responses:
        '204':
          description: Revoked
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
  /admin/api/users:
    get:
      summary: List users (admin UI alias)
      tags:
      - Admin Users
      - Admin UI Aliases
      responses:
        '200':
          description: Users
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuthUser'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Auth DB missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - name: search
        in: query
        required: false
        schema:
          type: string
      description: Alias of `/api/admin/users`.
    post:
      summary: Create user (admin UI alias)
      tags:
      - Admin Users
      - Admin UI Aliases
      responses:
        '201':
          description: Created user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthUserInput'
      description: Alias of `/api/admin/users`.
  /admin/api/users/{id}:
    get:
      summary: Get user (admin UI alias)
      tags:
      - Admin Users
      - Admin UI Aliases
      responses:
        '200':
          description: User
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      description: Alias of `/api/admin/users/{id}`.
    put:
      summary: Update user (admin UI alias)
      tags:
      - Admin Users
      - Admin UI Aliases
      responses:
        '200':
          description: Updated user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          description: Invalid payload/id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Applies email, enabled, and group_ids. username/password in body are ignored. Alias
        of `/api/admin/users/{id}`.
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthUserInput'
  /admin/api/users/{id}/password:
    post:
      summary: Reset user password (admin UI alias)
      tags:
      - Admin Users
      - Admin UI Aliases
      responses:
        '200':
          description: Password updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusOK'
        '400':
          description: Invalid payload/id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - password
              properties:
                password:
                  type: string
                  minLength: 1
              additionalProperties: false
      description: Alias of `/api/admin/users/{id}/password`.
  /admin/api/groups:
    get:
      summary: List groups (admin UI alias)
      tags:
      - Admin Groups
      - Admin UI Aliases
      responses:
        '200':
          description: Groups
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Group'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      description: Alias of `/api/admin/groups`.
    post:
      summary: Create group (admin UI alias)
      tags:
      - Admin Groups
      - Admin UI Aliases
      responses:
        '201':
          description: Created group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Group'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupInput'
      description: Alias of `/api/admin/groups`.
  /admin/api/groups/{id}:
    put:
      summary: Update group (admin UI alias)
      tags:
      - Admin Groups
      - Admin UI Aliases
      responses:
        '200':
          description: Updated group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Group'
        '400':
          description: Validation/protected group error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupInput'
      description: Alias of `/api/admin/groups/{id}`.
    delete:
      summary: Delete group (admin UI alias)
      tags:
      - Admin Groups
      - Admin UI Aliases
      responses:
        '204':
          description: Deleted
        '400':
          description: Protected group or invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      description: Alias of `/api/admin/groups/{id}`.
  /admin/api/groups/{id}/users:
    post:
      summary: Add user to group (admin UI alias)
      tags:
      - Admin Groups
      - Admin UI Aliases
      responses:
        '200':
          description: Membership ensured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusOK'
        '400':
          description: Invalid id/body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user_id
              properties:
                user_id:
                  type: integer
                  minimum: 1
              additionalProperties: false
      description: Alias of `/api/admin/groups/{id}/users`.
  /admin/api/groups/{id}/users/{user_id}:
    delete:
      summary: Remove user from group (admin UI alias)
      tags:
      - Admin Groups
      - Admin UI Aliases
      responses:
        '204':
          description: Removed (also when membership already absent)
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      - name: user_id
        in: path
        required: true
        schema:
          type: integer
          minimum: 1
      description: Alias of `/api/admin/groups/{id}/users/{user_id}`.
  /admin/api/resources:
    get:
      summary: List resources (admin UI alias)
      tags:
      - Admin ACL
      - Admin UI Aliases
      responses:
        '200':
          description: Resources
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Resource'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - name: service
        in: query
        schema:
          type: string
      - name: resource_type
        in: query
        schema:
          type: string
      description: Alias of `/api/admin/resources`.
  /admin/api/resource-access-overview:
    get:
      summary: Resource access overview (admin UI alias)
      tags:
      - Admin ACL
      - Admin UI Aliases
      responses:
        '200':
          description: Overview rows
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/ResourceAccessOverview'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - name: search
        in: query
        schema:
          type: string
      - name: service
        in: query
        schema:
          type: string
      - name: resource_type
        in: query
        schema:
          type: string
      - name: visibility
        in: query
        schema:
          type: string
          enum:
          - public
          - restricted
      description: Alias of `/api/admin/resource-access-overview`.
  /admin/api/resources/{id}/permissions:
    get:
      summary: Get resource ACL matrix (admin UI alias)
      tags:
      - Admin ACL
      - Admin UI Aliases
      responses:
        '200':
          description: ACL detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResourceACLDetail'
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      description: Alias of `/api/admin/resources/{id}/permissions`.
    put:
      summary: Replace resource ACL matrix (admin UI alias)
      tags:
      - Admin ACL
      - Admin UI Aliases
      responses:
        '200':
          description: ACL replaced
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusOK'
        '400':
          description: Invalid visibility/body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReplaceResourceACLInput'
      description: Alias of `/api/admin/resources/{id}/permissions`.
  /admin/api/permissions:
    get:
      summary: List permission rows (admin UI alias)
      tags:
      - Admin ACL
      - Admin UI Aliases
      responses:
        '200':
          description: Permissions
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                - total
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Permission'
                  total:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      description: Alias of `/api/admin/permissions`.
    post:
      summary: Grant permission row (admin UI alias)
      tags:
      - Admin ACL
      - Admin UI Aliases
      responses:
        '201':
          description: Granted permission
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Permission'
        '400':
          description: Invalid permission/body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PermissionInput'
      description: Alias of `/api/admin/permissions`.
  /admin/api/permissions/{id}:
    delete:
      summary: Revoke permission row (admin UI alias)
      tags:
      - Admin ACL
      - Admin UI Aliases
      responses:
        '204':
          description: Revoked
        '400':
          description: Invalid id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
      - cookieAuth: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdPath'
      description: Alias of `/api/admin/permissions/{id}`.
  /login:
    get:
      summary: Login page
      tags:
      - Sessions
      responses:
        '200':
          description: HTML login form
        '303':
          description: Already authenticated redirect
      security: []
    post:
      summary: Password login
      tags:
      - Sessions
      responses:
        '303':
          description: Success redirect with Set-Cookie acugis_session
        '400':
          description: Form parse failure
        '401':
          description: Invalid credentials (HTML)
        '503':
          description: Auth DB not configured
        '500':
          description: Login failure
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - username
              - password
              properties:
                username:
                  type: string
                password:
                  type: string
                next:
                  type: string
                  description: Same-host relative path
  /logout:
    get:
      summary: Logout (GET)
      tags:
      - Sessions
      responses:
        '303':
          description: Clears cookie and redirects to /login
      security:
      - {}
      - cookieAuth: []
    post:
      summary: Logout (POST)
      tags:
      - Sessions
      responses:
        '303':
          description: Clears cookie and redirects to /login
      security:
      - {}
      - cookieAuth: []
components:
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: acugis_session
      description: Opaque session token issued by POST /login. Server stores SHA-256 hash only.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: agp_PAT
      description: Personal access token with agp_ prefix.
  parameters:
    IdPath:
      name: id
      in: path
      required: true
      schema:
        type: integer
        minimum: 1
  schemas:
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
      additionalProperties: false
      example:
        error: authentication required
    Health:
      type: object
      required:
      - status
      properties:
        status:
          type: string
          const: ok
      additionalProperties: false
    StatusOK:
      type: object
      required:
      - status
      properties:
        status:
          type: string
          const: ok
      additionalProperties: false
    MeResponse:
      type: object
      required:
      - authenticated
      - user
      - admin
      - server_time
      properties:
        authenticated:
          type: boolean
        user:
          oneOf:
          - type: "null"
          - type: object
            required:
            - username
            - email
            - groups
            properties:
              username:
                type: string
              email:
                type: string
              groups:
                type: array
                items:
                  type: string
        admin:
          type: boolean
        server_time:
          type: string
          format: date-time
    PersonalAccessToken:
      type: object
      properties:
        id:
          type: integer
        user_id:
          type: integer
        name:
          type: string
        token_prefix:
          type: string
        scopes:
          type: array
          items:
            type: string
        expires_at:
          type:
          - string
          - 'null'
          format: date-time
        last_used_at:
          type:
          - string
          - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        revoked_at:
          type:
          - string
          - 'null'
          format: date-time
        active:
          type: boolean
    CreatePATInput:
      type: object
      required:
      - name
      properties:
        name:
          type: string
          minLength: 1
        expires_at:
          type:
          - string
          - 'null'
          description: RFC3339 timestamp or YYYY-MM-DD
      additionalProperties: false
    CreatedPATResponse:
      type: object
      required:
      - token
      - secret
      - message
      properties:
        token:
          $ref: '#/components/schemas/PersonalAccessToken'
        secret:
          type: string
          description: Returned once
        message:
          type: string
    StubCollection:
      type: object
      required:
      - items
      - total
      properties:
        items:
          type: array
          items: {}
        total:
          type: integer
        note:
          type: string
    CatalogItem:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
        title:
          type: string
        description:
          type: string
        url:
          type: string
        service:
          type: string
        category:
          type: string
        tags:
          type: array
          items:
            type: string
        updated_at:
          type: string
        owner:
          type: string
    CatalogResponse:
      type: object
      required:
      - datasets
      - maps
      - dashboards
      - reports
      properties:
        datasets:
          type: array
          items:
            $ref: '#/components/schemas/CatalogItem'
        maps:
          type: array
          items:
            $ref: '#/components/schemas/CatalogItem'
        dashboards:
          type: array
          items:
            $ref: '#/components/schemas/CatalogItem'
        reports:
          type: array
          items:
            $ref: '#/components/schemas/CatalogItem'
        warnings:
          type: array
          items:
            type: string
    Card:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        description:
          type: string
        card_type:
          type: string
          enum:
          - map
          - dashboard
          - report
          - dataset
          - external
          - admin
        url:
          type: string
        icon:
          type: string
        image_url:
          type: string
        category:
          type: string
        sort_order:
          type: integer
        is_public:
          type: boolean
        source_service:
          type: string
        source_resource_id:
          type: string
        is_imported:
          type: boolean
        created_at:
          type: string
          format: date-time
    CardInput:
      type: object
      required:
      - title
      - card_type
      - url
      properties:
        title:
          type: string
          minLength: 1
        description:
          type: string
        card_type:
          type: string
          enum:
          - map
          - dashboard
          - report
          - dataset
          - external
          - admin
        url:
          type: string
          minLength: 1
        icon:
          type: string
        image_url:
          type: string
        category:
          type: string
        sort_order:
          type: integer
        is_public:
          type: boolean
        source_service:
          type: string
        source_resource_id:
          type: string
        is_imported:
          type: boolean
      additionalProperties: false
    PortalCollection:
      type: object
      required:
      - items
      - total
      - categories
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Card'
        total:
          type: integer
        categories:
          type: array
          items:
            type: string
        note:
          type: string
    ReorderItem:
      type: object
      required:
      - id
      - sort_order
      properties:
        id:
          type: integer
          minimum: 1
        sort_order:
          type: integer
    Service:
      type: object
      properties:
        service_key:
          type: string
        display_name:
          type: string
        discovery_url:
          type: string
    DiscoveredResource:
      type: object
      properties:
        source_service:
          type: string
        source_resource_id:
          type: string
        title:
          type: string
        description:
          type: string
        url:
          type: string
        card_type:
          type: string
        icon:
          type: string
        image_url:
          type: string
        category:
          type: string
    DiscoveryResult:
      type: object
      properties:
        service:
          $ref: '#/components/schemas/Service'
        resource_type:
          type: string
        items:
          type: array
          items:
            $ref: '#/components/schemas/DiscoveredResource'
        total:
          type: integer
        fetched_at:
          type: string
          format: date-time
    AuthUser:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        email:
          type: string
        enabled:
          type: boolean
        is_admin:
          type: boolean
        groups:
          type: array
          items:
            type: string
        group_ids:
          type: array
          items:
            type: integer
        created_at:
          type: string
          format: date-time
    AuthUserInput:
      type: object
      properties:
        username:
          type: string
        email:
          type: string
        password:
          type: string
        enabled:
          type:
          - boolean
          - 'null'
        group_ids:
          type: array
          items:
            type: integer
      additionalProperties: false
    Group:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
        member_count:
          type: integer
        created_at:
          type: string
          format: date-time
    GroupInput:
      type: object
      required:
      - name
      properties:
        name:
          type: string
          minLength: 1
        description:
          type: string
      additionalProperties: false
    Resource:
      type: object
      properties:
        id:
          type: integer
        resource_type:
          type: string
        resource_key:
          type: string
        display_name:
          type: string
        service:
          type: string
        created_by:
          type: integer
        created_at:
          type: string
          format: date-time
    Permission:
      type: object
      properties:
        id:
          type: integer
        resource_id:
          type: integer
        resource_type:
          type: string
        resource_key:
          type: string
        display_name:
          type: string
        service:
          type: string
        group_id:
          type: integer
        group_name:
          type: string
        permission:
          type: string
          enum:
          - view
          - edit
          - admin
        created_at:
          type: string
          format: date-time
    PermissionInput:
      type: object
      required:
      - resource_id
      - group_id
      - permission
      properties:
        resource_id:
          type: integer
          minimum: 1
        group_id:
          type: integer
          minimum: 1
        permission:
          type: string
          enum:
          - view
          - edit
          - admin
      additionalProperties: false
    ResourceAccessOverview:
      type: object
      properties:
        resource_id:
          type: integer
        resource_type:
          type: string
        resource_key:
          type: string
        display_name:
          type: string
        service:
          type: string
        owner_id:
          type: integer
        owner_name:
          type: string
        visibility:
          type: string
          enum:
          - public
          - restricted
        group_names_with_access:
          type: array
          items:
            type: string
    GroupACLMatrixRow:
      type: object
      properties:
        group_id:
          type: integer
        group_name:
          type: string
        view:
          type: boolean
        edit:
          type: boolean
        admin:
          type: boolean
    ResourceACLDetail:
      type: object
      properties:
        resource:
          $ref: '#/components/schemas/Resource'
        visibility:
          type: string
          enum:
          - public
          - restricted
        matrix:
          type: array
          items:
            $ref: '#/components/schemas/GroupACLMatrixRow'
    ReplaceResourceACLInput:
      type: object
      required:
      - visibility
      - matrix
      properties:
        visibility:
          type: string
          enum:
          - public
          - restricted
        matrix:
          type: array
          items:
            $ref: '#/components/schemas/GroupACLMatrixRow'
