# Proposed contract, version 1: endpoints, errors, paging, idempotency, security and the capability of every
# operation. Resource schemas were initialized from the data model (docs/06-data) and are kept aligned with it; input
# schemas (*Input, *Request) and views are specific to this contract. Status: proposal for review, not confirmed design.
# Conventions: docs/05-interfaces/choosing-an-interface.md. Vendor extensions: x-moonitoring-audience (who calls),
# x-moonitoring-capability (the capability checked), x-moonitoring-entitlement (the entitlement feature checked),
# x-moonitoring-requirements (the requirements of 1.requisiti-raffinati.txt the operation serves).
openapi: 3.1.0
info:
  title: Farm Sync API
  version: 1.0.0
  description: The farm sync client and its runs. The client enrols with a one-time code, reads its feeds and schedules,
    opens a run by announcing the hashes it holds, uploads only the new files through signed URLs and commits the
    run; it reports failures and daily digests. Organization users manage their clients and schedules and follow
    the runs; staff see every run and client.
servers:
- url: https://farm.moonitoring.com/sync/v1
  description: Prod
- url: https://stage-farm.moonitoring.com/sync/v1
  description: Stage, through IAP
security:
- bearer: []
tags:
- name: Enrolment
  description: A new or reinstalled client
- name: Client
  description: What the client reads and reports
- name: Runs
  description: Synchronisations
- name: Clients
  description: Sync clients of a farm
- name: Schedules
  description: When a client synchronises
- name: Digests
  description: Fingerprints of the client's cache
- name: Stats
  description: Counts for the Console and the Organization Dashboard
paths:
  /enrolments/redeem:
    post:
      summary: Redeem an enrolment code
      operationId: redeemEnrolment
      description: 'Called by a new or reinstalled client, without a token. Redemption and its receipt are recorded
        atomically. A retry with the same Idempotency-Key and the same request, including the public key, returns
        the recorded result, even after the code was consumed or expired; it does not create another account or
        replace a credential again. A different request with that key, or a new key using a consumed code, returns
        409. The receipt is bound to the enrolment code and the fingerprint of the submitted public key. On a reinstall the new installation
        replaces the old one, the previous ServiceAccount credential is disabled, and feeds, schedules and digests
        stay.'
      tags:
      - Enrolment
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EnrolmentRedeemRequest'
      responses:
        '201':
          description: The enrolled client credentials and feed configuration returned after the code is redeemed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrolmentResult'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
        '429':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/TooManyRequests
      security: []
      x-moonitoring-audience: client
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '67'
      - '67.2'
      - 1.*.2
      - '118'
  /sync-clients/me:
    get:
      summary: Read my configuration
      operationId: getMySyncClient
      description: 'Returns the client''s active feeds, schedules, and synchronization settings so it can decide what
        to read and when to contact Farm; also the farm''s cloud vendor feeds, read-only, and the cloud vendors it may
        offer.'
      tags:
      - Client
      responses:
        '200':
          description: The client's current configuration, including the feeds and schedules it must synchronize.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncClientConfiguration'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_clients.read
      x-moonitoring-requirements:
      - '146'
      - '67'
      - '44.7'
      - '58'
  /sync-clients/me/installation:
    post:
      summary: Report my installation
      operationId: reportMyInstallation
      description: Records which Releases installation is running this client so Farm can connect synchronization activity to the installed build,
        and the Windows name of the computer it runs on, which the Console and the Organization Dashboard show.
      tags:
      - Client
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InstallationReport'
      responses:
        '200':
          description: The updated client registration after its running installation is recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncClient'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_runs.write
      x-moonitoring-requirements:
      - '67.2'
      - '67.6'
  /feeds/{feed_id}/sync-runs:
    post:
      summary: Open a sync run
      operationId: openSyncRun
      description: Starts a delivery for one source feed. The client declares the files it already holds so Farm can identify new inputs and return upload targets for them.
      tags:
      - Runs
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: feed_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The configured source feed for this farm. It identifies the channel whose files and records this sync run delivers.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncRunOpenRequest'
      responses:
        '201':
          description: The opened run and upload instructions for the files that are new to Farm.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncRunOpened'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_runs.write
      x-moonitoring-requirements:
      - '52.27'
      - '52.28'
      - '1027'
      - '135'
      - '137'
      - '52'
      - '52.3'
      - '52.18'
      - '52.20'
      - '30'
      - '17'
      - '1002.25'
  /sync-runs/{run_id}/commit:
    post:
      summary: Commit a sync run
      operationId: commitSyncRun
      description: Finalizes a run after the client has uploaded its announced files. Farm records the delivery and dispatches the received raw files for parsing.
      tags:
      - Runs
      parameters:
      - name: run_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncRunCommitRequest'
      responses:
        '200':
          description: The committed run, including the delivery state that parsing will process.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncRunResponse'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_runs.write
      x-moonitoring-requirements:
      - '52'
      - '52.14'
      - '52.16'
      - '52.17'
      - '30'
      - '51'
  /sync-runs/{run_id}/fail:
    post:
      summary: Fail a sync run
      operationId: failSyncRun
      description: Records that the client could not complete delivery, including the failure class and where the client stopped, so the run can be diagnosed and retried.
      tags:
      - Runs
      parameters:
      - name: run_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncRunFailRequest'
      responses:
        '200':
          description: The failed run with the recorded client-side failure details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncRunResponse'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_runs.write
      x-moonitoring-requirements:
      - '52.21'
      - '52.22'
      - '52.24'
  /sync-runs/{run_id}/heartbeat:
    post:
      summary: Renew a running sync run
      operationId: heartbeatSyncRun
      description: The client calls it every few minutes while the run is running. A run that stops calling is failed as abandoned by the farm sweeper, and a later commit of it is refused.
      tags:
      - Runs
      parameters:
      - name: run_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncRunResponse'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_runs.write
      x-moonitoring-requirements:
      - '52.25'
      - '52.26'
  /sync-runs/{run_id}/log-url:
    post:
      summary: Get an upload link for the run log
      operationId: getSyncRunLogUploadUrl
      description: A signed URL to put the JSONL run log next to the raw files.
      tags:
      - Runs
      parameters:
      - name: run_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The signed URL used to upload the run log.
          content:
            application/json:
              schema:
                $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SignedUrl
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_runs.write
      x-moonitoring-requirements:
      - '52.23'
  /feeds/{feed_id}/digests:
    post:
      summary: Report digests
      operationId: reportDigests
      description: Reports the client's monthly fingerprints for one source feed. Farm compares them with its stored digests and requests a full snapshot when a month no longer matches.
      tags:
      - Digests
      parameters:
      - name: feed_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The configured source feed whose client cache is being fingerprinted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DigestReport'
      responses:
        '200':
          description: The digest comparison for each reported month of the source feed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DigestResult'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_runs.write
      x-moonitoring-requirements:
      - '59'
    get:
      summary: List a feed's digests
      operationId: listDigests
      description: Returns the stored monthly fingerprints for one source feed so a client or operator can compare delivery coverage and detect drift.
      tags:
      - Digests
      parameters:
      - name: feed_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The configured source feed whose stored digests are being read.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/SyncDigest'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '59'
  /feeds/{feed_id}/known-keys:
    get:
      summary: Download the keys we hold
      operationId: listKnownKeys
      description: At setup, so a reinstalled client carries on where the old one stopped.
      tags:
      - Digests
      parameters:
      - name: feed_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The configured source feed whose source-record keys Farm already holds.
      - name: month
        in: query
        required: false
        schema:
          type: string
        description: Only this month.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/KnownRecordKey'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: client
      x-moonitoring-capability: farm.sync_clients.read
      x-moonitoring-requirements:
      - '59'
  /organizations/{organization_id}/sync-runs:
    get:
      summary: List a farm's sync runs
      operationId: listOrganizationSyncRuns
      description: Returns the synchronization history visible to an Organization, optionally narrowed to a source feed and status, so the Dashboard can show delivery and processing progress.
      tags:
      - Runs
      parameters:
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      - name: feed
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only this feed.
      - name: status
        in: query
        required: false
        schema:
          type: string
          enum:
          - running
          - done
          - failed
        description: Delivery status.
      - name: processing
        in: query
        required: false
        schema:
          type: string
          enum:
          - pending
          - done
          - partially_failed
          - failed
        description: Processing status, from the parse jobs.
      - name: from
        in: query
        required: false
        schema:
          type: string
          format: date-time
        description: Started at or after.
      - name: to
        in: query
        required: false
        schema:
          type: string
          format: date-time
        description: Started before.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/SyncRunView'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '52'
      - '1002.17'
      - '1006.20'
  /organizations/{organization_id}/sync-stats:
    get:
      summary: Count a farm's sync
      operationId: getOrganizationSyncStats
      description: 'Decided on 2026-11-11. The numbers at the top of the Dashboard''s Integrations page, counted by
        farm so the app does not add up runs itself: integrations working and needing a look, the last sync, and the
        files and failed runs of the last `days` days, in the farm''s time zone.'
      tags:
      - Stats
      parameters:
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: 'The farm: the token''s active Organization, or a farm that delegated to it.'
      - name: days
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 90
          default: 7
        description: How many days back the counts go, today included.
      responses:
        '200':
          description: The counts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrganizationSyncStats'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: organization
      x-moonitoring-capability: farm.ingestion.read
      x-moonitoring-requirements:
      - '44'
      - '52'
      - '1002.17'
  /sync-stats:
    get:
      summary: Count every farm's sync
      operationId: getSyncStats
      description: 'Decided on 2026-11-11. The Console''s basic numbers on sync: active clients, clients with a run in
        the last 24 hours, and per day the runs that went well or failed and the files received or failed, over the
        last `days` days in UTC.'
      tags:
      - Stats
      parameters:
      - name: days
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 90
          default: 7
        description: How many days back the counts go, today included.
      responses:
        '200':
          description: The counts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncStats'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.ingestion.read_all
      x-moonitoring-requirements:
      - '52'
      - '67'
      - '1006.20'
  /sync-runs:
    get:
      summary: List every sync run
      operationId: listSyncRuns
      description: Returns the staff-wide synchronization history, with filters for farm, delivery status, and failure class.
      tags:
      - Runs
      parameters:
      - name: farm
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only this farm.
      - name: status
        in: query
        required: false
        schema:
          type: string
          enum:
          - running
          - done
          - failed
        description: Delivery status.
      - name: failure_class
        in: query
        required: false
        schema:
          type: string
          enum:
          - source_unreachable
          - credentials_rejected
          - source_changed
          - unsupported_source
          - timeout
          - api_unreachable
          - storage_error
          - abandoned
        description: Only this failure class.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/SyncRunView'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.ingestion.read_all
      x-moonitoring-requirements:
      - '52'
      - '1006.20'
  /sync-runs/{run_id}:
    get:
      summary: Read a sync run
      operationId: getSyncRun
      description: 'Returns one run with its delivery and parser-derived processing state. Organization users need
        farm.ingestion.read for the run''s farm; staff need farm.ingestion.read_all. A sync client may instead use
        farm.sync_clients.read only for its own runs: its active ServiceAccount must be registered to the run''s
        sync_client, and the run''s source_feed must belong to that client''s farm and remain assigned to that client.
        The server checks these bindings on every read; a guessed run ID or a capability alone grants no access
        to another client''s run.'
      tags:
      - Runs
      parameters:
      - name: run_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The synchronization run and its delivery and processing state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncRunView'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      - farm.sync_clients.read
      x-moonitoring-requirements:
      - '52'
  /sync-runs/{run_id}/log-download-url:
    post:
      summary: Get the run log
      operationId: getSyncRunLogUrl
      description: Returns a signed download URL for the JSONL log uploaded alongside the run's raw files.
      tags:
      - Runs
      parameters:
      - name: run_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The signed URL used to download the run log.
          content:
            application/json:
              schema:
                $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SignedUrl
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '52.23'
  /organizations/{organization_id}/sync-clients:
    get:
      summary: List a farm's sync clients
      operationId: listOrganizationSyncClients
      description: Returns the active or retired synchronization clients registered for an Organization.
      tags:
      - Clients
      parameters:
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      - name: include_retired
        in: query
        required: false
        schema:
          type: boolean
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/SyncClient'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.read
      - farm.sync_clients.read_all
      x-moonitoring-requirements:
      - '67'
      - '1002.22'
      - '1006.21'
    post:
      summary: Register a sync client
      operationId: createSyncClient
      description: 'Creates a synchronization client for an Organization so staff or Organization users can issue enrolment
        codes and configure its feeds and schedules. `farm.sync_clients.manage` is an admin capability: a
        person''s sign-in must be at most five minutes old, otherwise 403 with the problem type
        reauthentication-required. Staff with `farm.sync_clients.manage_all` are not asked: staff tokens carry
        no auth_time.'
      tags:
      - Clients
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncClientInput'
      responses:
        '201':
          description: The newly registered synchronization client.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncClient'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.manage
      - farm.sync_clients.manage_all
      x-moonitoring-requirements:
      - '67'
      - '67.3'
      - '1002.23'
  /sync-clients:
    get:
      summary: List every sync client
      operationId: listSyncClients
      description: Returns the staff-wide client registry, optionally filtered to one farm or client status.
      tags:
      - Clients
      parameters:
      - name: farm
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only this farm.
      - name: status
        in: query
        required: false
        schema:
          type: string
          enum:
          - active
          - retired
        description: Only this status.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/SyncClient'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.sync_clients.read_all
      x-moonitoring-requirements:
      - '67'
      - '1006.21'
  /sync-clients/{client_id}:
    get:
      summary: Read a sync client
      operationId: getSyncClient
      description: Returns one client's registration, status, and farm association for authorized administration or troubleshooting.
      tags:
      - Clients
      parameters:
      - name: client_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The registered synchronization client and its current status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncClient'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.read
      - farm.sync_clients.read_all
      x-moonitoring-requirements:
      - '67'
  /sync-clients/{client_id}/enrolment-codes:
    post:
      summary: Issue an enrolment code
      operationId: createEnrolmentCode
      description: 'For the first setup or a reinstallation. The code expires; issuing a new one voids the previous. Every
        issue is a PlatformAuditEvent when staff do it. `farm.sync_clients.manage` is an admin capability: a
        person''s sign-in must be at most five minutes old, otherwise 403 with the problem type
        reauthentication-required. Staff with `farm.sync_clients.manage_all` are not asked: staff tokens carry
        no auth_time.'
      tags:
      - Clients
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: client_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '201':
          description: The newly issued enrolment code and its expiry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrolmentCode'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.manage
      - farm.sync_clients.manage_all
      x-moonitoring-requirements:
      - '67.2'
      - '1002.23'
      - '1006.21'
  /sync-clients/{client_id}/retire:
    post:
      summary: Retire a sync client
      operationId: retireSyncClient
      description: 'The farm stopped using it: its service account is disabled, the validity of its feeds is closed.
        Publishes `sync_client.retired.v1`. `farm.sync_clients.manage` is an admin capability: a person''s
        sign-in must be at most five minutes old, otherwise 403 with the problem type reauthentication-required.
        Staff with `farm.sync_clients.manage_all` are not asked: staff tokens carry no auth_time.'
      tags:
      - Clients
      parameters:
      - name: client_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The retired synchronization client after its service account and feed validity are closed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncClient'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.manage
      - farm.sync_clients.manage_all
      x-moonitoring-requirements:
      - '67.4'
      - '1002.23'
  /sync-clients/{client_id}/schedules:
    get:
      summary: List a client's schedules
      operationId: listSchedules
      description: Returns the client's default schedule and any source-feed-specific schedule exceptions.
      tags:
      - Schedules
      parameters:
      - name: client_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/SyncSchedule'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.read
      - farm.sync_clients.read_all
      x-moonitoring-requirements:
      - '58'
      - '1002.22'
    post:
      summary: Add a schedule
      operationId: createSchedule
      description: 'A schedule without a feed is the client''s default; one with a feed is an exception for that feed.
        `farm.sync_clients.manage` is an admin capability: a person''s sign-in must be at most five minutes old,
        otherwise 403 with the problem type reauthentication-required. Staff with `farm.sync_clients.manage_all`
        are not asked: staff tokens carry no auth_time.'
      tags:
      - Schedules
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: client_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncScheduleInput'
      responses:
        '201':
          description: The new synchronization schedule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncSchedule'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.manage
      - farm.sync_clients.manage_all
      x-moonitoring-requirements:
      - '58'
      - '1002.23'
      - '1006.21'
  /schedules/{schedule_id}:
    patch:
      summary: Change a schedule
      operationId: updateSchedule
      description: 'Replaces a client''s default or source-feed-specific recurrence rule and enabled state.
        `farm.sync_clients.manage` is an admin capability: a person''s sign-in must be at most five minutes old,
        otherwise 403 with the problem type reauthentication-required. Staff with `farm.sync_clients.manage_all`
        are not asked: staff tokens carry no auth_time.'
      tags:
      - Schedules
      parameters:
      - name: schedule_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncScheduleInput'
      responses:
        '200':
          description: The changed synchronization schedule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncSchedule'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.manage
      - farm.sync_clients.manage_all
      x-moonitoring-requirements:
      - '58.4'
      - '58.5'
      - '1002.23'
      - '1006.21'
    delete:
      summary: Delete a schedule
      operationId: deleteSchedule
      description: 'Removes a synchronization schedule so the client no longer runs that default or source-feed-specific
        recurrence. `farm.sync_clients.manage` is an admin capability: a person''s sign-in must be at most five
        minutes old, otherwise 403 with the problem type reauthentication-required. Staff with
        `farm.sync_clients.manage_all` are not asked: staff tokens carry no auth_time.'
      tags:
      - Schedules
      parameters:
      - name: schedule_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '204':
          description: Done.
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.sync_clients.manage
      - farm.sync_clients.manage_all
      x-moonitoring-requirements:
      - '58'
      - '1002.23'
components:
  schemas:
    SyncStats:
      type: object
      description: Counts of every farm's sync for the Console (decided 2026-11-11), computed by farm from its runs.
      properties:
        days:
          type: integer
          description: The days counted, as asked.
        active_clients:
          type: integer
          description: Sync clients that are active, not retired.
        clients_with_run_24h:
          type: integer
          description: Active sync clients that started a run in the last 24 hours.
        per_day:
          type: array
          items:
            $ref: '#/components/schemas/SyncStatsDay'
          description: One entry per UTC day, oldest first, today included; a day with no run has zeros.
      required:
      - days
      - active_clients
      - clients_with_run_24h
      - per_day
    SyncStatsDay:
      type: object
      description: 'The runs of one day, by the day they started. A run went well when it was delivered and every file
            was read; it failed when its delivery failed or some of its files could not be read. A run still sending
            or waiting to be read counts in neither.'
      properties:
        date:
          type: string
          format: date
        runs_ok:
          type: integer
        runs_failed:
          type: integer
        files_received:
          type: integer
          description: Raw files the day's runs delivered.
        files_failed:
          type: integer
          description: Raw files of the day's runs that could not be read, their parse failed.
      required:
      - date
      - runs_ok
      - runs_failed
      - files_received
      - files_failed
    OrganizationSyncStats:
      type: object
      description: 'Counts of one farm''s sync for the Organization Dashboard (decided 2026-11-11). An integration is a
        feed of the farm: it works when its last run was delivered and its files are read or being read, and needs a
        look when its last run failed or its files could not be read; a paused one, or one with no run yet, counts in
        neither.'
      properties:
        days:
          type: integer
          description: The days counted, as asked, in the farm's time zone.
        integrations_working:
          type: integer
        integrations_needing_a_look:
          type: integer
        last_sync_at:
          type: string
          format: date-time
          description: When the farm's latest run started; absent before the first.
        files_received:
          type: integer
          description: Raw files its runs delivered in the days counted.
        failed_runs:
          type: integer
          description: 'Runs of the days counted that failed. A run went well when it was delivered and every file
            was read; it failed when its delivery failed or some of its files could not be read. A run still sending
            or waiting to be read counts in neither.'
      required:
      - days
      - integrations_working
      - integrations_needing_a_look
      - files_received
      - failed_runs
    SyncRunResponse:
      description: API response view of a sync run, not the persistence schema of SyncRun. An open or failed run
        may have no raw inputs, manifest, log or computed counts. Completion fields are required by delivery state.
        One synchronisation of a feed, and what it covered. Comparing what the source sent with what
        we already hold reveals added, changed and removed records, including deletions made at the source. Its
        status says only whether the client delivered; whether we processed what it delivered is read from its ParseJobs.
        A run that fails before reaching our API is reported by the client at its next contact, so a feed with no
        run at all is silent, not failed.
      type: object
      properties:
        deadline_at:
          type: string
          format: date-time
          description: When the run must be finished; after it, or after ten minutes without a heartbeat, the farm
            sweeper fails the run as abandoned.
        last_heartbeat_at:
          type: string
          format: date-time
        adapter:
          type: string
          format: uuid
          description: ID of the SourceAdapter that read the source; absent when none covered the detected version.
        detection:
          type: object
          description: What the client found at the source before choosing an adapter.
          properties:
            product:
              type: string
              description: The product as the source names itself, e.g. DelPro; absent when nothing was recognised.
            version:
              type: string
              description: The version string as read at the source, unchanged.
            fingerprint:
              type: string
              description: Hash of the structure signature; equal fingerprints mean the same structure.
            structure_signature:
              type: string
              description: Table and column names, file headers or API fields as JSON, without values.
          required:
          - fingerprint
          - structure_signature
        id:
          type: string
          format: uuid
          description: Key.
        source_feed:
          type: string
          format: uuid
          description: ID of the configured source feed for the farm; it identifies the channel whose records this run covers.
        kind:
          type: string
          enum:
          - full_snapshot
          - incremental
          description: '`full_snapshot`: everything the source has for the covered period. Only a full snapshot
            can reveal deletions, unless the source sends them explicitly (e.g. ICAR ADE isDeleted). Our farm client
            sends incremental runs with explicit deletions, detected against its local cache; setup and a digest
            mismatch (SyncDigest) trigger a `full_snapshot` of the affected period.'
        coverage:
          allOf:
          - $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Period
          description: The time span of the data the sync covers.
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        status:
          type: string
          enum:
          - running
          - done
          - failed
          description: 'Delivery only: whether the client read the source and committed the raw files to us. Processing
            by the parsers is read from the run''s ParseJobs (ParseJob), never stored here: pending while any job
            is pending or running; done when every job is done; `partially_failed` when some failed and some are
            done; failed when every job failed. The Console shows delivery and processing side by side.'
        added:
          type: integer
        changed:
          type: integer
        removed:
          type: integer
        unchanged:
          type: integer
        resource_scope:
          type: array
          items:
            $ref: ../../../../common/v1/schemas.yaml#/components/schemas/CodedValue
          minItems: 1
        filter_definition:
          allOf:
          - $ref: ../../../../common/v1/schemas.yaml#/components/schemas/CodedValue
          description: Versioned query/table/resource filters, not just a time period.
        raw_inputs:
          type: array
          items:
            type: string
            format: uuid
          description: IDs of the available RawMetadata. Empty before any file is received, after an early failure,
            or for an empty delivery. An empty list alone never proves that source records were deleted.
        source_order_key:
          type: string
          description: Source snapshot/change sequence; comparison semantics are adapter-defined, not arrival timestamp.
        snapshot_verified:
          type: boolean
          default: false
          description: True only after all pages/resources succeeded and source completeness/deletion semantics
            were established.
        received_keys_manifest:
          type: string
          minLength: 1
          description: Immutable inventory of source keys before canonical filtering, so parse failures cannot simulate
            deletions.
        trigger:
          type: string
          enum:
          - setup
          - schedule
          - digest_mismatch
          - manual
          description: '`setup`: first sync after installation; schedule: SyncSchedule; `digest_mismatch`: SyncDigest
            differs.'
        sync_client:
          type: string
          format: uuid
          description: ID of the SyncClient. The client that ran it; empty for feeds that reach us without our client.
        attempt:
          type: integer
          default: 1
          description: A retry of the same planned run is a new SyncRun with the same coverage and attempt + 1.
        failed_step:
          type: string
          enum:
          - connect
          - fetch
          - verify_snapshot
          - store_raw
          - commit
          description: 'Where a failed run stopped, on the client''s side only; only when status = failed, and absent
            for failure_class = abandoned, whose step is unknown (decided 2026-11-05). The values
            parse, diff and resolve were removed: they belong to ParseJob and to the processing state above. connect:
            the source or our API could not be reached; fetch: reading the source; `verify_snapshot`: completeness
            check; `store_raw`: upload to the bucket; commit: the commit call to our API.'
        last_error:
          type: string
          description: Why it failed, in one line a person can act on (e.g. "login refused by DelPro", "page 7 of
            12 timed out").
        failure_class:
          type: string
          enum:
          - source_unreachable
          - credentials_rejected
          - source_changed
          - unsupported_source
          - timeout
          - api_unreachable
          - storage_error
          - abandoned
          description: Set only when status = failed. Transient classes (`source_unreachable`, timeout, `api_unreachable`,
            `storage_error`) are retried by the client within the night (attempt + 1); `credentials_rejected` and
            `source_changed` are not retried and raise an issue for the Organization and for support.
        detail_path:
          type: string
          description: 'Run log in the bucket next to the raw files (JSONL: step, resource, page, counts, timing,
            error). Stack traces stay in Cloud Logging; the facts that explain the run do not.'
        created_at:
          type: string
          format: date-time
      required:
      - id
      - source_feed
      - kind
      - coverage
      - started_at
      - status
      - resource_scope
      - filter_definition
      - raw_inputs
      - snapshot_verified
      - trigger
      - attempt
      - created_at
      allOf:
      - if:
          properties:
            status:
              const: done
          required:
          - status
        then:
          required:
          - finished_at
          - added
          - changed
          - removed
          - unchanged
          - received_keys_manifest
      - if:
          properties:
            status:
              const: failed
          required:
          - status
        then:
          required:
          - finished_at
          - failure_class
          - last_error
        else:
          not:
            anyOf:
            - required: [failed_step]
            - required: [failure_class]
    SyncClient:
      description: 'The farm sync client, as the data side sees it: one stable identity per farm client, with the
        farm it works for and the feeds it reads. Its software, version and updates are its current installation,
        like any other application we distribute; reinstalling the software replaces the installation and keeps
        the client, its feeds, schedules and digests.'
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key. Issued once and kept when the software is reinstalled.
        installation:
          type: string
          format: uuid
          description: 'Unique. ID of the ReleaseInstallation. The installation currently running this client; must
            be an installation of the sync client application. Empty between an uninstall and the next setup. Setup
            re-attaches a reinstalled client with an enrolment code issued to the farm: the new Installation replaces
            the old one here and feeds, schedules and digests stay. Previous installations are kept by `system_version`.'
        farm:
          type: string
          format: uuid
          description: ID of the Organization that is a Farm. One client, one farm. If the farm installs it to read
            another farm's software, that is its responsibility.
        status:
          type: string
          enum:
          - active
          - retired
          default: active
          description: '`retired`: the farm stopped using the client; the validity of its feeds is closed.'
        service_account:
          type: string
          format: uuid
          description: ID of the ServiceAccount in access the client signs in as; a reinstallation replaces it and
            disables the previous one.
        client_version:
          type: string
          description: The version label of the build the client last reported, for troubleshooting.
        client_build_number:
          type: integer
          description: The build number the client last reported; compared with minimum_build_number.
        computer_name:
          type: string
          description: The Windows name of the computer the client runs on, as it last reported it (req 67.6). Not
            unique and never an identity; the client is known by its id.
        system_version:
          $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SystemVersion
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - farm
      - status
      - system_version
      - created_at
      - updated_at
    SyncSchedule:
      description: When a farm's sync client synchronises, as a recurrence in the farm's timezone, with optional
        exceptions for single feeds. The Organization sets it, and Moonitoring support can change it.
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key.
        sync_client:
          type: string
          format: uuid
          description: ID of the SyncClient.
        source_feed:
          type: string
          format: uuid
          description: ID of the configured source feed. Empty selects the client's default schedule; a value creates an exception for that feed, such as synchronizing a robot more often than a registry.
        rule:
          type: string
          description: Recurrence in the farm timezone, e.g. every day at 00:00, every 4 hours, Monday and Thursday
            at 02:00 (iCalendar RRULE).
        enabled:
          type: boolean
          default: true
        updated_by:
          type: string
          description: The User or MoonitoringUser who last changed it.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - sync_client
      - rule
      - enabled
      - updated_by
      - created_at
      - updated_at
    SyncDigest:
      description: A monthly fingerprint, per feed, of what the farm's sync client believes it has sent. If it does
        not match what we hold, that month is synchronised again in full.
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key.
        source_feed:
          type: string
          format: uuid
          description: ID of the configured source feed that produced the synchronized records.
        month:
          type: string
          description: e.g. 2026-10, in the farm timezone.
        record_count:
          type: integer
        keys_hash:
          type: string
          description: 'SHA-256 in lower-case hex over the month''s pairs, sorted by `source_record_key` in the byte
            order of its UTF-8, each written as `<source_record_key>` TAB `<content_hash>` LF (decided 2026-11-05). A
            month with no record hashes nothing: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.'
        reported_at:
          type: string
          format: date-time
        matches:
          type: boolean
        created_at:
          type: string
          format: date-time
      required:
      - id
      - source_feed
      - month
      - record_count
      - keys_hash
      - reported_at
      - matches
      - created_at
    SyncClientInput:
      type: object
      description: Registers a sync client for a farm before its first enrolment.
      properties:
        farm:
          type: string
          format: uuid
          description: ID of the Farm.
      required:
      - farm
    EnrolmentCode:
      type: object
      description: A one-time code, shown once, that a technician types into the client at setup. It binds the installation
        to this SyncClient and its farm.
      properties:
        code:
          type: string
        sync_client:
          type: string
          format: uuid
        expires_at:
          type: string
          format: date-time
      required:
      - code
      - sync_client
      - expires_at
    EnrolmentRedeemRequest:
      type: object
      description: The client redeems its code with the public key it generated locally; the private key never leaves
        the farm computer.
      properties:
        code:
          type: string
        public_key:
          type: object
          properties:
            kty:
              type: string
            crv:
              type: string
            x:
              type: string
            kid:
              type: string
          required:
          - kty
        installation:
          type: string
          format: uuid
          description: ID of the ReleaseInstallation, when the client already enrolled with releases.
        client_version:
          type: string
      required:
      - code
      - public_key
    EnrolmentResult:
      type: object
      description: 'The identity the client will use (decided 2026-10-30, D3): a ServiceAccount of the farm''s Organization,
        created by access with the public key of the request and the global role Sync Client (`farm.sync_runs.write`,
        `farm.sync_clients.read`); its owner is the person who issued the enrolment code. Tokens are then obtained
        from access with `private_key_jwt` and last fifteen minutes. Retiring the sync client disables the account.'
      properties:
        sync_client:
          $ref: '#/components/schemas/SyncClient'
        service_account:
          type: string
          format: uuid
          description: ID of the User (ServiceAccount) the client authenticates as.
        organization:
          type: string
          format: uuid
        token_endpoint:
          type: string
          format: uri
        known_keys_available:
          type: boolean
          description: 'True when the farm already has data: the client should download the known keys before its
            first run.'
      required:
      - sync_client
      - service_account
      - organization
      - token_endpoint
    SyncClientConfiguration:
      type: object
      description: 'Everything the client needs after sign-in: its identity, the feeds it reads, their schedules
        and the current timezone of the farm.'
      properties:
        sync_client:
          $ref: '#/components/schemas/SyncClient'
        farm_timezone:
          type: string
        feeds:
          type: array
          items:
            type: object
            additionalProperties: true
            description: SourceFeed, as in the Sources API.
        schedules:
          type: array
          items:
            $ref: '#/components/schemas/SyncSchedule'
        digest_months:
          type: integer
          description: How many recent months the daily digest must cover.
          default: 3
        cloud_feeds:
          type: array
          description: 'The farm''s cloud vendor feeds (decided 2026-11-10): its active feeds on a ThirdPartyPlatformChannel,
            which the vendor puller of farm reads and which have no `sync_client`. Read-only for the client''s token: the
            client shows them as cards and opens no run on them; removing one is `disconnectFeed` with a person''s token
            signed in at most five minutes before.'
          items:
            type: object
            properties:
              feed:
                type: object
                additionalProperties: true
                description: SourceFeed, as in the Sources API.
              last_run:
                allOf:
                - $ref: '#/components/schemas/SyncRunView'
                description: The latest run of the puller for this feed; absent before the first.
            required:
            - feed
        cloud_vendors:
          type: array
          description: 'The cloud vendors the client may offer in its catalogue (decided 2026-11-10): the ThirdPartyPlatformChannels
            with an active AdapterMapping of side `puller` that applies to the farm, global or of its Organization. No
            other cloud vendor is offered.'
          items:
            type: object
            properties:
              source_channel:
                type: string
                format: uuid
                description: ID of the SourceChannel, a ThirdPartyPlatformChannel.
              name:
                type: string
                description: The channel's name, e.g. AIA.
              adapter_code:
                type: string
                description: The puller that reads it, e.g. aia.api; the client chooses the set-up form by it.
            required:
            - source_channel
            - name
            - adapter_code
        adapter_mappings:
          type: array
          description: 'Ordered by precedence (decided 2026-11-05): the feed''s own mappings, then its Organization''s, then the
            global ones; the client takes the first entry whose range covers its detected version. A bare version is one
            version (3.0.2 means =3.0.2), and with no detected version only a mapping of every version (*) applies. Every
            active client-side mapping that applies to the farm, global or of its Organization, plus the feed-scoped
            mappings of the client''s own feeds (decided 2026-11-11), so a client with no feed yet still has the catalogue
            of the software it may set up; the client reads a detected version with the adapter mapped to it, and with
            none it fails the run as unsupported_source.'
          items:
            type: object
            properties:
              source_channel:
                type: string
                format: uuid
              version_range:
                type: string
              adapter_code:
                type: string
              adapter_version:
                type: string
              minimum_build_number:
                type: integer
                description: The lowest build of the farm sync client that carries the adapter; a client with a lower build
                  number refuses the run, reports the gap and updates itself through its channel.
              source_feed:
                type: string
                format: uuid
                description: Set when the mapping applies to one feed only.
            required:
            - source_channel
            - version_range
            - adapter_code
            - adapter_version
      required:
      - sync_client
      - farm_timezone
      - feeds
      - schedules
    SyncRunOpenRequest:
      type: object
      description: 'Opens a run for one feed with the files the client read, or reports a run that failed before
        reaching us (status failed with its failed step). An empty files list is allowed for an early failure or
        an empty delivery. Gate 1 reuses only objects whose uncompressed hash and size were verified by the server
        and which remain available; a matching hash alone is not enough. A matching metadata entry with a missing
        or erased object returns 409, never known or a replacement upload target; recovery is outside this handshake.
        A withdrawn file cannot be reactivated by opening a run and returns 409.'
      properties:
        adapter:
          type: object
          description: The client adapter that read the source; absent when none covered the detected version, in
            which case the run is reported failed with failure_class unsupported_source.
          properties:
            code:
              type: string
            adapter_version:
              type: string
          required:
          - code
          - adapter_version
        detection:
          type: object
          description: What the client found at the source before choosing an adapter.
          properties:
            product:
              type: string
              description: The product as the source names itself, e.g. DelPro; absent when nothing was recognised.
            version:
              type: string
              description: The version string as read at the source, unchanged.
            fingerprint:
              type: string
              description: Hash of the structure signature; equal fingerprints mean the same structure.
            structure_signature:
              type: string
              description: Table and column names, file headers or API fields as JSON, without values.
          required:
          - fingerprint
          - structure_signature
        kind:
          type: string
          enum:
          - full_snapshot
          - incremental
          description: '`full_snapshot`: everything the source has for the covered period. Only a full snapshot
            can reveal deletions, unless the source sends them explicitly (e.g. ICAR ADE isDeleted). Our farm client
            sends incremental runs with explicit deletions, detected against its local cache; setup and a digest
            mismatch (SyncDigest) trigger a `full_snapshot` of the affected period.'
        coverage:
          allOf:
          - $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Period
          description: The time span of the data the sync covers.
        trigger:
          type: string
          enum:
          - setup
          - schedule
          - digest_mismatch
          - manual
          description: '`setup`: first sync after installation; schedule: SyncSchedule; `digest_mismatch`: SyncDigest
            differs.'
        attempt:
          type: integer
          default: 1
          description: A retry of the same planned run is a new SyncRun with the same coverage and attempt + 1.
        started_at:
          type: string
          format: date-time
        resource_scope:
          type: array
          items:
            $ref: ../../../../common/v1/schemas.yaml#/components/schemas/CodedValue
          minItems: 1
        filter_definition:
          allOf:
          - $ref: ../../../../common/v1/schemas.yaml#/components/schemas/CodedValue
          description: Versioned query/table/resource filters, not just a time period.
        source_order_key:
          type: string
          description: Source snapshot/change sequence; comparison semantics are adapter-defined, not arrival timestamp.
        files:
          type: array
          items:
            type: object
            description: One file the client read, before upload.
            properties:
              hash_type:
                type: string
                enum:
                - xxh3
                - sha256
                - md5
              data_hash:
                type: string
              data_size:
                type: integer
              compressed_size:
                type: integer
              source_producer:
                type: object
                properties:
                  origin_version:
                    type: string
                    format: uuid
                  channel_version:
                    type: string
                    format: uuid
                  produced_at:
                    type: string
                    format: date-time
                  device:
                    type: string
                    format: uuid
                required:
                - origin_version
              resource:
                type: string
                description: Which table or export of the source this file is, for the run log.
            required:
            - hash_type
            - data_hash
            - data_size
            - source_producer
        status:
          type: string
          enum:
          - running
          - failed
          description: failed only for a deferred report of a run that never reached the commit.
        failed_step:
          type: string
          enum:
          - connect
          - fetch
          - verify_snapshot
          - store_raw
          - commit
          description: 'Where a failed run stopped, on the client''s side only; only when status = failed. The values
            parse, diff and resolve were removed: they belong to ParseJob and to the processing state above. connect:
            the source or our API could not be reached; fetch: reading the source; `verify_snapshot`: completeness
            check; `store_raw`: upload to the bucket; commit: the commit call to our API.'
        failure_class:
          type: string
          enum:
          - source_unreachable
          - credentials_rejected
          - source_changed
          - unsupported_source
          - timeout
          - api_unreachable
          - storage_error
          description: Set only when status = failed. Transient classes (`source_unreachable`, timeout, `api_unreachable`,
            `storage_error`) are retried by the client within the night (attempt + 1); `credentials_rejected` and
            `source_changed` are not retried and raise an issue for the Organization and for support.
        last_error:
          type: string
          description: Why it failed, in one line a person can act on (e.g. "login refused by DelPro", "page 7 of
            12 timed out").
        finished_at:
          type: string
          format: date-time
      required:
      - kind
      - coverage
      - trigger
      - started_at
      - resource_scope
      - filter_definition
      - files
      allOf:
      - if:
          properties:
            status:
              const: failed
          required:
          - status
        then:
          required:
          - finished_at
          - failed_step
          - failure_class
          - last_error
        else:
          not:
            anyOf:
            - required: [failed_step]
            - required: [failure_class]
    SyncRunOpened:
      type: object
      description: The run and, per announced file, whether it is already known or must be uploaded.
      properties:
        sync_run:
          $ref: '#/components/schemas/SyncRunResponse'
        files:
          type: array
          items:
            type: object
            properties:
              data_hash:
                type: string
              known:
                type: boolean
                description: True only for an available object whose content the server verified; says nothing
                  about processing or snapshot completeness for this delivery.
              raw_file:
                type: string
                format: uuid
                description: ID of the existing RawMetadata when known.
              upload:
                $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SignedUrl
            required:
            - data_hash
            - known
            allOf:
            - if:
                properties:
                  known:
                    const: true
                required:
                - known
              then:
                required:
                - raw_file
                not:
                  required:
                  - upload
              else:
                required:
                - upload
                not:
                  required:
                  - raw_file
      required:
      - sync_run
      - files
    SyncRunCommitRequest:
      type: object
      description: 'Commits the run once every upload finished and its stored object is verified and available:
        one transaction records the SyncRun as done, one RawMetadata per new file and one ParseJob per file to parse.
        Idempotent by run. An empty raw-input list is allowed but is not evidence of an empty source. Inferred
        deletions require a verified full snapshot, a complete immutable keys manifest (including an explicitly
        empty inventory), identical resource/filter scope and authoritative source ordering. Missing any of these
        prevents inferred deletions; incremental runs delete only through explicit tombstones.'
      properties:
        received_keys_manifest:
          type: string
          minLength: 1
          description: Immutable inventory of source keys before canonical filtering, so parse failures cannot simulate
            deletions.
        snapshot_verified:
          type: boolean
          default: false
          description: True only after all pages/resources succeeded and source completeness/deletion semantics
            were established.
        source_order_key:
          type: string
          description: Source snapshot/change sequence; comparison semantics are adapter-defined, not arrival timestamp.
        added:
          type: integer
        changed:
          type: integer
        removed:
          type: integer
        unchanged:
          type: integer
        finished_at:
          type: string
          format: date-time
        detail_path:
          type: string
          description: 'Run log in the bucket next to the raw files (JSONL: step, resource, page, counts, timing,
            error). Stack traces stay in Cloud Logging; the facts that explain the run do not.'
      required:
      - received_keys_manifest
      - added
      - changed
      - removed
      - unchanged
      - finished_at
    SyncRunFailRequest:
      type: object
      description: Marks an open run failed on the client's side.
      properties:
        failed_step:
          type: string
          enum:
          - connect
          - fetch
          - verify_snapshot
          - store_raw
          - commit
          description: 'Where a failed run stopped, on the client''s side only; only when status = failed. The values
            parse, diff and resolve were removed: they belong to ParseJob and to the processing state above. connect:
            the source or our API could not be reached; fetch: reading the source; `verify_snapshot`: completeness
            check; `store_raw`: upload to the bucket; commit: the commit call to our API.'
        failure_class:
          type: string
          enum:
          - source_unreachable
          - credentials_rejected
          - source_changed
          - unsupported_source
          - timeout
          - api_unreachable
          - storage_error
          description: Set only when status = failed. Transient classes (`source_unreachable`, timeout, `api_unreachable`,
            `storage_error`) are retried by the client within the night (attempt + 1); `credentials_rejected` and
            `source_changed` are not retried and raise an issue for the Organization and for support.
        last_error:
          type: string
          description: Why it failed, in one line a person can act on (e.g. "login refused by DelPro", "page 7 of
            12 timed out").
        finished_at:
          type: string
          format: date-time
      required:
      - failed_step
      - failure_class
      - last_error
      - finished_at
    SyncRunView:
      type: object
      description: 'A run with its processing state, derived from its parse jobs: pending, done, partially_failed
        or failed. Delivery and processing are shown side by side.'
      properties:
        sync_run:
          $ref: '#/components/schemas/SyncRunResponse'
        processing:
          type: string
          enum:
          - pending
          - done
          - partially_failed
          - failed
        jobs:
          type: object
          properties:
            pending:
              type: integer
            running:
              type: integer
            done:
              type: integer
            failed:
              type: integer
      required:
      - sync_run
      - processing
    DigestReport:
      type: object
      description: The client's fingerprint per feed and month of what it believes it has sent.
      properties:
        months:
          type: array
          items:
            type: object
            properties:
              month:
                type: string
                pattern: ^\d{4}-\d{2}$
              record_count:
                type: integer
              keys_hash:
                type: string
            required:
            - month
            - record_count
            - keys_hash
        reported_at:
          type: string
          format: date-time
      required:
      - months
      - reported_at
    DigestResult:
      type: object
      description: Per month, whether the digest matches ours. A mismatch asks the client for a full snapshot of
        that month.
      properties:
        months:
          type: array
          items:
            type: object
            properties:
              month:
                type: string
              matches:
                type: boolean
              digest:
                $ref: '#/components/schemas/SyncDigest'
            required:
            - month
            - matches
      required:
      - months
    KnownRecordKey:
      type: object
      description: A record we already hold for the feed, so a reinstalled client does not resend it as new.
      properties:
        source_record_key:
          type: string
        content_hash:
          type: string
        month:
          type: string
      required:
      - source_record_key
      - content_hash
      - month
    SyncScheduleInput:
      type: object
      description: Input to create or change a SyncSchedule; server-managed fields are left out.
      properties:
        source_feed:
          type: string
          format: uuid
          description: ID of the configured source feed. Empty selects the client's default schedule; a value creates an exception for that feed, such as synchronizing a robot more often than a registry.
        rule:
          type: string
          description: Recurrence in the farm timezone, e.g. every day at 00:00, every 4 hours, Monday and Thursday
            at 02:00 (iCalendar RRULE).
        enabled:
          type: boolean
          default: true
      required:
      - rule
      - enabled
      additionalProperties: false
    InstallationReport:
      type: object
      description: The client reports the ReleaseInstallation it runs as, after enrolling with releases, and the
        computer it runs on; it reports again when either changes.
      properties:
        installation:
          type: string
          format: uuid
        computer_name:
          type: string
          minLength: 1
          maxLength: 255
          description: The Windows computer name, as the operating system gives it. Shown to tell a farm's computers
            apart; not unique and never an identity. Absent when the client cannot read it.
      required:
      - installation
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'The short-lived token signed by `access`: an Organization user token, a staff token or a client
        token, as the operation''s audience says.'
