openapi: 3.1.0
info:
  title: GeoSync HTTP integration surface
  version: 1.0.0
  description: |
    Source-derived contract for GeoSync's session-authenticated PHP surface.
    This is not a versioned REST API. Most legacy actions report business
    failure as HTTP 200 with success=false. Upstream Analysis, Form Builder,
    provider, and publisher payloads are intentionally open schemas.
servers:
  - url: https://example.invalid/geosync-service
    description: Replace with the deployment origin and mount.
security:
  - cookieAuth: []
tags:
  - name: Proxy
  - name: Admin actions
  - name: Admin HTML
  - name: Downloads
paths:
  /api/proxy.php:
    get:
      tags: [Proxy]
      summary: Invoke an allowlisted GET operation
      parameters:
        - $ref: '#/components/parameters/Service'
        - $ref: '#/components/parameters/ProxyGetPath'
        - name: connection_id
          in: query
          schema: { type: integer, minimum: 1 }
        - name: schema
          in: query
          schema: { type: string, maxLength: 63 }
      responses:
        '200':
          description: Local JSON result or unmodified upstream representation.
          content:
            application/json:
              schema: {}
              examples:
                tables:
                  value:
                    schemas: [public]
                    tables:
                      - { schema: public, table: sample_points, geom: geom }
                openUpstream:
                  value: { note: upstream-defined payload }
        '400': { $ref: '#/components/responses/PlainOrJsonBadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '502': { $ref: '#/components/responses/BadGateway' }
        default: { $ref: '#/components/responses/OpenUpstream' }
    post:
      tags: [Proxy]
      summary: Invoke an allowlisted POST operation
      parameters:
        - $ref: '#/components/parameters/Service'
        - $ref: '#/components/parameters/ProxyPostPath'
      requestBody:
        required: true
        content:
          application/json:
            schema: {}
            examples:
              safeAnalysis:
                value:
                  connection_id: 1
                  analysis_type: points
      responses:
        '200':
          description: Upstream JSON or Form Builder ZIP.
          content:
            application/json: { schema: {} }
            application/zip:
              schema: { type: string, contentEncoding: binary }
        '400': { $ref: '#/components/responses/PlainOrJsonBadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '502': { $ref: '#/components/responses/BadGateway' }
        default: { $ref: '#/components/responses/OpenUpstream' }

  /admin/projects.php:
    get:
      tags: [Admin HTML]
      summary: List projects as authenticated admin HTML
      responses:
        '200': { $ref: '#/components/responses/HtmlPage' }
        '303': { $ref: '#/components/responses/LoginRedirect' }
  /admin/project_settings.php:
    get:
      tags: [Admin HTML]
      summary: Read project settings HTML
      parameters:
        - $ref: '#/components/parameters/IdQuery'
        - name: tab
          in: query
          schema:
            type: string
            enum: [frequency, synchronize, files]
            default: frequency
      responses:
        '200': { $ref: '#/components/responses/HtmlPage' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFoundText' }

  /admin/action/backend.php:
    post:
      tags: [Admin actions]
      summary: Control a project synchronization service
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [id, op]
              properties:
                id: { $ref: '#/components/schemas/PositiveId' }
                op:
                  type: string
                  enum: [enable, disable, start, stop, restart]
            example: { id: 12, op: start }
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
  /admin/action/project.php:
    post:
      tags: [Admin actions]
      summary: Create, update, delete, or inspect a project
      description: Exactly one discriminator should be present. pwd_vis and conn_info return secrets.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/ProjectSave'
                - $ref: '#/components/schemas/IdDelete'
                - $ref: '#/components/schemas/ProjectConnInfo'
                - $ref: '#/components/schemas/ProjectPassword'
            examples:
              save:
                value:
                  save: '1'
                  id: 0
                  name: Demo project
                  server_id: 2
                  project_id: safe-provider-id
                  geopackage: demo
                  pglink_id: 3
                  schema: demo_sync
                  rsync_ids: []
              connection: { value: { conn_info: '1', id: 12 } }
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
  /admin/action/server.php:
    post:
      tags: [Admin actions]
      summary: Manage providers and query provider metadata
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/ServerSave'
                - $ref: '#/components/schemas/IdDelete'
                - $ref: '#/components/schemas/ServerQuery'
                - type: object
                  required: [list_servers]
                  properties: { list_servers: { type: string } }
            example: { list_projects: '1', id: 2 }
      responses:
        '200':
          description: Action result or provider list/metadata. Provider objects are upstream-defined.
          content:
            application/json:
              schema: {}
  /admin/action/pglink.php:
    post:
      tags: [Admin actions]
      summary: Manage PostgreSQL links
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/PgLinkSave'
                - $ref: '#/components/schemas/IdDelete'
                - $ref: '#/components/schemas/GenericIdAction'
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
  /admin/action/rsync.php:
    post:
      tags: [Admin actions]
      summary: Manage or run rsync links
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/RsyncSave'
                - $ref: '#/components/schemas/IdDelete'
                - $ref: '#/components/schemas/GenericIdAction'
            example:
              save: '1'
              id: 0
              name: Safe remote target
              sync_type: remote
              host: files.example.invalid
              port: 22
              username: sync-user
              password: dummy-not-a-real-secret
              folder: /srv/geosync/demo
              usr_grp: www-data:www-data
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
        '303':
          description: Redirect after excludes update.
  /admin/action/daemon.php:
    post:
      tags: [Admin actions]
      summary: Update daemon settings
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - type: object
                  required: [set, id, param, val]
                  properties:
                    set: { type: string }
                    id: { $ref: '#/components/schemas/PositiveId' }
                    param:
                      type: string
                      enum: [skip_init, force_init, single_run, sleep_time, retention]
                    val: { type: string }
                - type: object
                  required: [save, id]
                  additionalProperties: true
                  properties:
                    save: { type: string }
                    id: { $ref: '#/components/schemas/PositiveId' }
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
  /admin/action/user.php:
    post:
      tags: [Admin actions]
      summary: Manage local users
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - type: object
                  required: [save, id, name, email, password, accesslevel]
                  properties:
                    save: { type: string }
                    id: { type: integer, minimum: -1 }
                    name: { type: string, maxLength: 250 }
                    email: { type: string, format: email, maxLength: 250 }
                    password: { type: string, format: password, writeOnly: true }
                    accesslevel: { type: string, enum: [Admin, User] }
                - $ref: '#/components/schemas/IdDelete'
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
  /admin/action/group.php:
    post:
      tags: [Admin actions]
      summary: Manage legacy project groups
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - type: object
                  required: [save, id, name, project_ids]
                  properties:
                    save: { type: string }
                    id: { type: integer, minimum: 0 }
                    name: { type: string, maxLength: 250 }
                    project_ids:
                      type: array
                      items: { $ref: '#/components/schemas/PositiveId' }
                - $ref: '#/components/schemas/IdDelete'
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
  /admin/action/geonode.php:
    post:
      tags: [Admin actions]
      summary: Manage GeoNode SSH targets
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/SshTargetSave'
                - $ref: '#/components/schemas/IdDelete'
                - $ref: '#/components/schemas/GenericIdAction'
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }
  /admin/action/gstore.php:
    post:
      tags: [Admin actions]
      summary: Manage GeoServer stores and layers
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: true
              required: [id]
              properties:
                id: { type: integer, minimum: 0 }
                save: { type: string }
                delete: { type: string }
                pwd_vis: { type: string }
                details_info: { type: string }
                store_tables: { type: string }
                create_layer: { type: string }
                name: { type: string, maxLength: 250 }
                project_id: { $ref: '#/components/schemas/PositiveId' }
                url: { type: string, format: uri, maxLength: 250 }
                username: { type: string }
                password: { type: string, format: password, writeOnly: true }
                workspace: { type: string, maxLength: 250 }
                layer_name: { type: string }
                store_tbl: { type: string }
                update_geonode: { type: string, enum: ['t'] }
                geonode_id: { $ref: '#/components/schemas/PositiveId' }
      responses:
        '200': { $ref: '#/components/responses/OpenAction' }
  /admin/action/publish_web.php:
    post:
      tags: [Admin actions]
      summary: Publish or republish a project
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [id]
              properties: { id: { $ref: '#/components/schemas/PositiveId' } }
      responses:
        '200':
          description: Publish action result; business failures also use 200.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ActionResult'
                  - type: object
                    properties:
                      publisher_project_id: { type: string }
                      viewer_url: { type: string, format: uri }
                      published_at: { type: string }
  /admin/action/express.php:
    post:
      tags: [Admin actions]
      summary: List provider projects or provision an express project
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: true
              properties:
                list_qfield_projects: { type: string }
                list_mergin_projects: { type: string }
                create_express: { type: string }
                service_id: { type: integer, minimum: 1 }
                provider: { type: string, enum: [qfield, mergin, existing] }
                qf_proj_id: { type: string }
                qf_user: { type: string }
                qf_pass: { type: string, format: password, writeOnly: true }
                qf_url: { type: string, format: uri }
                mergin_user: { type: string }
                mergin_pass: { type: string, format: password, writeOnly: true }
                mergin_url: { type: string, format: uri }
                workspace: { type: string }
                q: { type: string }
                limit: { type: integer, minimum: 1, default: 50 }
                sleep_time: { type: integer, minimum: 60, default: 600 }
      responses:
        '200': { $ref: '#/components/responses/OpenAction' }
  /admin/action/table_loader.php:
    post:
      tags: [Admin actions]
      summary: Load project table data for DataTables
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: true
              required: [id, geom_tbl, tbl_columns]
              properties:
                id: { $ref: '#/components/schemas/PositiveId' }
                geom_tbl: { type: string }
                geom_col: { type: string, default: geom }
                tbl_columns: { type: string, description: URL-encoded comma-separated names. }
                eps: { type: integer, minimum: 0, default: 0 }
                minpoints: { type: integer, minimum: 0, default: 0 }
                is_centroid: { type: integer, enum: [0, 1], default: 0 }
                where: { type: string }
      responses:
        '200':
          description: DataTables JSON, or plain-text logical error.
          content:
            application/json: { schema: {} }
            text/plain: { schema: { type: string } }

  /admin/action/files.php:
    get:
      tags: [Downloads]
      summary: Download a project file/image or clean archives
      parameters:
        - $ref: '#/components/parameters/IdQuery'
        - name: gpkg
          in: query
          schema: { type: string }
        - name: img
          in: query
          schema: { type: string }
        - name: thumb
          in: query
          schema: { type: integer, enum: [1] }
        - name: clean
          in: query
          schema: { type: boolean }
      responses:
        '200':
          description: File representation.
          content:
            application/octet-stream:
              schema: { type: string, contentEncoding: binary }
            image/png:
              schema: { type: string, contentEncoding: binary }
        '302':
          description: Redirect after failure or clean operation.
  /admin/action/svc_log.php:
    get:
      tags: [Downloads]
      summary: Download a project service log
      parameters: [{ $ref: '#/components/parameters/IdQuery' }]
      responses:
        '200':
          description: Log attachment.
          content:
            text/plain: { schema: { type: string } }
    post:
      tags: [Admin actions]
      summary: Empty a project service log
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [id, op]
              properties:
                id: { $ref: '#/components/schemas/PositiveId' }
                op: { type: string, enum: [empty] }
      responses:
        '200': { $ref: '#/components/responses/ActionResult' }

  /admin/action/__server.php:
    post:
      tags: [Admin actions]
      summary: Invoke the legacy provider-management duplicate
      description: |
        Directly addressable stale duplicate of server.php. It supports provider
        save, delete, password display, project/GeoPackage listing, and project
        information, but not list_servers. Use server.php for new integrations.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: true
              properties:
                id: { type: integer, minimum: 0 }
                save: { type: string }
                delete: { type: string }
                pwd_vis: { type: string }
                list_projects: { type: string }
                list_geopackages: { type: string }
                list_project_geopackages: { type: string }
                project_info: { type: string }
                project_id: { type: string }
      responses:
        '200':
          description: Legacy action result or upstream-defined provider data.
          content:
            application/json: { schema: {} }

  /admin/action/login.php:
    post:
      security: []
      tags: [Admin actions]
      summary: Authenticate with a local email and password
      description: |
        In Central mode this redirects to Central login without checking the
        submitted body. In local/keycloak mode it verifies a local password and
        populates the PHP session. This is a browser redirect flow, not JSON.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [submit, email, pwd]
              properties:
                submit: { type: string, minLength: 1 }
                email: { type: string, format: email }
                pwd: { type: string, format: password, writeOnly: true }
      responses:
        '302':
          description: Local login success or failure redirect.
          headers:
            Location: { schema: { type: string, format: uri-reference } }
        '303':
          description: Redirect to Central login when Central mode is active.
          headers:
            Location: { schema: { type: string, format: uri-reference } }

  /admin/action/signup.php:
    post:
      security: []
      tags: [Admin actions]
      summary: Disabled public registration endpoint
      description: The registration gate always terminates this endpoint before legacy signup logic.
      responses:
        '403':
          description: Registration is disabled.
          content:
            text/html: { schema: { type: string } }

  /admin/action/verify.php:
    get:
      security: []
      tags: [Admin actions]
      summary: Disabled public registration verification endpoint
      description: The registration gate always terminates this endpoint before legacy verification logic.
      parameters:
        - name: id
          in: query
          schema: { type: integer, minimum: 1 }
        - name: verify
          in: query
          schema: { type: string }
      responses:
        '403':
          description: Registration is disabled.
          content:
            text/html: { schema: { type: string } }

components:
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: acugis_session
      description: |
        Central raw session cookie. Local/legacy deployments instead use their
        PHP session cookie; OpenAPI cannot express a dynamic PHP cookie name.
  parameters:
    IdQuery:
      name: id
      in: query
      required: true
      schema: { $ref: '#/components/schemas/PositiveId' }
    Service:
      name: service
      in: query
      required: true
      schema:
        type: string
        enum: [analysis, formbuilder, geosync]
    ProxyGetPath:
      name: path
      in: query
      required: true
      schema:
        type: string
        enum:
          - api/points
          - api/connections
          - api/schemas
          - api/tables
          - api/columns
          - api/qfield_servers
          - health
          - api/qfield_servers.php
    ProxyPostPath:
      name: path
      in: query
      required: true
      schema:
        type: string
        enum:
          - api/cluster
          - api/points-in-geometry
          - api/insights
          - api/save-config
          - generate
  schemas:
    PositiveId: { type: integer, minimum: 1 }
    ActionResult:
      type: object
      required: [success, message]
      additionalProperties: true
      properties:
        success: { type: boolean }
        message: { type: string }
        id: { type: integer, minimum: 1 }
      examples:
        - { success: true, message: Operation completed, id: 12 }
        - { success: false, message: Action not allowed! }
    IdDelete:
      type: object
      required: [delete, id]
      properties:
        delete: { type: string }
        id: { $ref: '#/components/schemas/PositiveId' }
    GenericIdAction:
      type: object
      required: [id]
      additionalProperties: true
      properties:
        id: { $ref: '#/components/schemas/PositiveId' }
        pwd_vis: { type: string }
        conn_info: { type: string }
        start: { type: string }
        stop: { type: string }
        log: { type: string }
        excludes: { type: string }
    ProjectSave:
      type: object
      required: [save, id, name, server_id, project_id, geopackage, pglink_id, schema]
      properties:
        save: { type: string }
        id: { type: integer, minimum: 0 }
        name: { type: string, minLength: 1, maxLength: 250 }
        server_id: { $ref: '#/components/schemas/PositiveId' }
        project_id: { type: string, minLength: 1, maxLength: 250 }
        geopackage: { type: string, minLength: 1, maxLength: 250 }
        pglink_id: { $ref: '#/components/schemas/PositiveId' }
        schema: { type: string, minLength: 1, maxLength: 50 }
        rsync_ids:
          type: array
          items: { $ref: '#/components/schemas/PositiveId' }
    ProjectConnInfo:
      type: object
      required: [conn_info, id]
      properties:
        conn_info: { type: string }
        id: { $ref: '#/components/schemas/PositiveId' }
    ProjectPassword:
      type: object
      required: [pwd_vis, id]
      properties:
        pwd_vis: { type: string }
        id: { $ref: '#/components/schemas/PositiveId' }
    ServerSave:
      type: object
      required: [save, id, name, type, url, username, password]
      properties:
        save: { type: string }
        id: { type: integer, minimum: 0 }
        name: { type: string, maxLength: 250 }
        type: { type: string, enum: [qf, db, gdb] }
        url: { type: string, format: uri, maxLength: 250 }
        username: { type: string, maxLength: 250 }
        password: { type: string, format: password, writeOnly: true, maxLength: 250 }
    ServerQuery:
      type: object
      required: [id]
      additionalProperties: true
      properties:
        id: { $ref: '#/components/schemas/PositiveId' }
        list_projects: { type: string }
        list_geopackages: { type: string }
        list_project_geopackages: { type: string }
        project_info: { type: string }
        pwd_vis: { type: string }
        project_id: { type: string }
    PgLinkSave:
      type: object
      required: [save, id, name, host, port, username, password, dbname]
      properties:
        save: { type: string }
        id: { type: integer, minimum: 0 }
        name: { type: string, maxLength: 250 }
        host: { type: string, maxLength: 250 }
        port: { type: integer, minimum: 1, maximum: 65535, default: 5432 }
        username: { type: string, maxLength: 250 }
        password: { type: string, format: password, writeOnly: true, maxLength: 250 }
        dbname: { type: string, maxLength: 80 }
    RsyncSave:
      type: object
      required: [save, id, name, sync_type]
      additionalProperties: false
      properties:
        save: { type: string }
        id: { type: integer, minimum: 0 }
        name: { type: string, maxLength: 250 }
        sync_type: { type: string, enum: [remote, local], default: remote }
        local_project_id: { $ref: '#/components/schemas/PositiveId' }
        host: { type: string, maxLength: 250 }
        port: { type: integer, minimum: 0, maximum: 65535 }
        username: { type: string, maxLength: 250 }
        password: { type: string, format: password, writeOnly: true, maxLength: 250 }
        folder: { type: string, maxLength: 250 }
        usr_grp: { type: string, maxLength: 50, default: 'www-data:www-data' }
    SshTargetSave:
      type: object
      required: [save, id, name, host, port, username, password, folder]
      properties:
        save: { type: string }
        id: { type: integer, minimum: 0 }
        name: { type: string, maxLength: 250 }
        host: { type: string, maxLength: 250 }
        port: { type: integer, minimum: 1, maximum: 65535, default: 22 }
        username: { type: string, maxLength: 250 }
        password: { type: string, format: password, writeOnly: true, maxLength: 250 }
        folder: { type: string, maxLength: 250 }
  responses:
    ActionResult:
      description: HTTP success transport; inspect the success member.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ActionResult' }
    OpenAction:
      description: Action result with operation-specific, open fields.
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ActionResult'
              - type: object
                additionalProperties: true
    HtmlPage:
      description: Authenticated HTML page.
      content:
        text/html: { schema: { type: string } }
    LoginRedirect:
      description: Redirect to login.
      headers:
        Location: { schema: { type: string, format: uri-reference } }
    Unauthorized:
      description: No valid session.
      content:
        text/plain: { schema: { type: string }, example: Unauthorized }
    Forbidden:
      description: Disallowed endpoint or insufficient permission.
      content:
        text/plain: { schema: { type: string } }
        application/json: { schema: { $ref: '#/components/schemas/ActionResult' } }
    PlainOrJsonBadRequest:
      description: Invalid service, parameters, or locally handled request.
      content:
        text/plain: { schema: { type: string } }
        application/json:
          schema:
            type: object
            required: [error]
            properties: { error: { type: string } }
    BadGateway:
      description: Upstream connection failed.
      content:
        text/plain: { schema: { type: string } }
    NotFoundText:
      description: Project/provider not found.
      content:
        text/plain: { schema: { type: string } }
    OpenUpstream:
      description: Status and representation controlled by the selected upstream.
      content:
        application/json: { schema: {} }
        text/plain: { schema: { type: string } }
