# Urvi App Service (BFF) v2 - OpenAPI 3.1 contract. Generated; edit with care.
openapi: 3.1.0
info:
  title: Urvi App Service (BFF) API
  version: 2.0.0-draft
  summary: Single backend-for-frontend for the Urvi Android app (RySS, ap.gov).
  description: |
    The **Urvi App Service** (backend-for-frontend) is the only backend the rewritten Urvi Android app talks to. It serves the RySS programme on tenant `ap.gov`.

    It owns login and tokens, the session bootstrap, an ETag-cached config bundle, hydrated farmer snapshots with cursor-based delta sync, an idempotent batch endpoint for every offline write, resumable file uploads, server-side push and PII-free telemetry. It hides the ~12 backend services, ~40 report datasets and query syntax the current app calls directly.

    **Conventions**
    - Base path `/gateway/urvi-app-service/api/v2`; additive changes only within v2; the current and two previous app releases are supported, older ones get 426 `VERSION_TOO_OLD`.
    - Every request sends `Authorization: Bearer <access token>` (except login / refresh), `X-App-Version`, `X-Device-Id`, optionally `X-Request-Id`, `X-Tenant` and `Accept-Language` (`en`, `te`, `ta`). Writes send `Idempotency-Key`.
    - Reads are online calls; **all user writes go through `POST /sync/batch`** (also when online). Files are uploaded first via `POST /files` and referenced by `fileId`.
    - Errors are RFC 9457 `application/problem+json` with a typed `code` (`ProblemCode`). Business-rule parameters are published in `ConfigBundle.formLimits` so client and server validate with the same numbers.
    - Units: area in **acres** (2 decimals), harvest and stock in **quintals**, seed in **kg**, distances in metres. Times are ISO 8601 with offset; dates are `yyyy-MM-dd`.
    - Aadhaar is write-only and returned masked (`XXXX-XXXX-1234`). No PII in logs or telemetry.
    - Each operation lists the current calls it replaces (`x-replaces`), the app features that use it (`x-features`) and the error codes it may return (`x-error-codes`).
  contact:
    name: Urvi platform team
    email: urvi-platform@example.invalid
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://{stagingHost}/gateway/urvi-app-service/api/v2
  description: Staging
  variables:
    stagingHost:
      default: staging.urvi.example.invalid
      description: Staging gateway host (placeholder).
- url: https://{prodHost}/gateway/urvi-app-service/api/v2
  description: Production
  variables:
    prodHost:
      default: urvi.example.invalid
      description: Production gateway host (placeholder).
security:
- bearerAuth: []
tags:
- name: Auth & session
  description: Login, token refresh, logout, session bootstrap and login history.
- name: Config
  description: 'Typed, ETag-cached config bundle: switches, season, masters, limits, KAP form schema.'
- name: Geography
  description: District > Mandal > Village > VO > Habitation > SHG lookups.
- name: Farmers
  description: Hydrated snapshot, delta changes, single farmer, search and abstract.
- name: Land & geofence
  description: Land parcels, leased land, land usage metrics and geofences (writes via Sync).
- name: Crops & harvest
  description: Crop Actual blocks (PMDS, A Grade) and crop harvests (writes via Sync).
- name: Farming models
  description: ATM plots, ATM harvests and 365 DKG answers (writes via Sync).
- name: Planning (KAP)
  description: KAP / annual action plan for the current season (writes via Sync).
- name: Endorsements
  description: VO / SHG / farmer endorsement state, comments, eligibility and abstracts (writes via Sync).
- name: Compact blocks
  description: Compact blocks and eligible tiles (writes via Sync).
- name: Surveys & assessments
  description: Certification survey, activity tracker, VO / SHG surveys (writes via Sync).
- name: Analytics
  description: Cadre and unit MIS dashboards.
- name: Notifications
  description: MIS and system notifications (backed by server push).
- name: Sync
  description: Idempotent batch of offline mutations and batch status.
- name: Files
  description: Resumable uploads that may precede their parent entity; signed downloads.
- name: Devices
  description: Device registration, push tokens, binding takeover and remote actions.
- name: Telemetry
  description: PII-free screen events and sync-health counters.
paths:
  /auth/login:
    post:
      operationId: login
      tags:
      - Auth & session
      summary: Log in with login, tenant and password
      description: |-
        **Use case.** Called by the Login screen on first launch (online only) and after "Session expired". The app sends the trimmed, lower-cased login, the tenant and the password together with its app-scoped device id; the App Service authenticates against Bridge server-side, resolves the tenant (including the federated `ap.gov` host), binds the refresh token to the device and returns short-lived tokens. The app stores only the refresh token (Keystore-backed) and immediately calls `GET /session/bootstrap`. The password is never persisted on the phone.

        **Rules.**
        - Login shapes accepted: `email`, `login@@tenant`, numeric IE login; a plain email gets `@@tenant` appended.
        - Unknown tenant -> 404 `TENANT_NOT_FOUND`; bad credentials -> 401 `INVALID_CREDENTIALS` (no detail about which part was wrong).
        - When the tenant enforces device binding and another device is bound, login still succeeds; bootstrap reports `CONFLICT`.
        - Rate limited per login and per device.

        **Error codes.** `INVALID_CREDENTIALS`, `TENANT_NOT_FOUND`, `DEVICE_BLOCKED`, `VERSION_TOO_OLD`, `RATE_LIMITED`
      x-replaces:
      - POST /ctld/api/session/v1
      - PUT /ctld/api/device/deviceid/v1
      x-features:
      - login
      - startup-routing
      x-error-codes:
      - INVALID_CREDENTIALS
      - TENANT_NOT_FOUND
      - DEVICE_BLOCKED
      - VERSION_TOO_OLD
      - RATE_LIMITED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            example:
              login: user.a@@ap.gov
              tenant: ap.gov
              password: example-password
              deviceId: 6b1f2c3d-0000-4000-8000-000000053249
      responses:
        '200':
          description: Logged in.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
              example:
                accessToken: eyJhbGciOiJSUzI1NiJ9.EXAMPLE.SIGNATURE
                tokenType: Bearer
                expiresIn: 900
                refreshToken: rt_EXAMPLE_0000000000000000000000
                refreshExpiresIn: 2592000
                userId: 9001
                tenant: ap.gov
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: Invalid credentials.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/invalid-credentials
                title: Login failed
                status: 401
                detail: User id or password is incorrect.
                code: INVALID_CREDENTIALS
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Unknown tenant.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/tenant-not-found
                title: Organisation not found
                status: 404
                detail: Organisation name not recognised.
                code: TENANT_NOT_FOUND
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
      security: []
  /auth/refresh:
    post:
      operationId: refreshToken
      tags:
      - Auth & session
      summary: Rotate the refresh token
      description: |-
        **Use case.** Called by the HTTP client's authenticator when any call returns 401 `TOKEN_EXPIRED` (once), and by the sync worker before a batch if the access token is near expiry. Returns a new access token and a new refresh token (rotation); the old refresh token becomes invalid. On failure the app shows "Session expired, log in" and keeps the outbox intact. Replaces the silent re-login with a stored password.

        **Rules.**
        - Refresh tokens are bound to `deviceId`; a token presented from another device is revoked -> `REFRESH_TOKEN_INVALID`.
        - Reuse of a rotated refresh token revokes the whole token family.

        **Error codes.** `REFRESH_TOKEN_INVALID`, `DEVICE_BLOCKED`
      x-replaces:
      - POST /ctld/api/session/v1
      x-features:
      - login
      - startup-routing
      - pending-sync
      x-error-codes:
      - REFRESH_TOKEN_INVALID
      - DEVICE_BLOCKED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshRequest'
            example:
              refreshToken: rt_EXAMPLE_0000000000000000000000
              deviceId: 6b1f2c3d-0000-4000-8000-000000053249
      responses:
        '200':
          description: New tokens.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
              example:
                accessToken: eyJhbGciOiJSUzI1NiJ9.EXAMPLE.SIGNATURE
                tokenType: Bearer
                expiresIn: 900
                refreshToken: rt_EXAMPLE_0000000000000000000000
                refreshExpiresIn: 2592000
                userId: 9001
                tenant: ap.gov
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: Refresh token invalid or expired; log in again.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/refresh-token-invalid
                title: Session expired
                status: 401
                detail: Please log in again.
                code: REFRESH_TOKEN_INVALID
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
      security: []
  /auth/logout:
    post:
      operationId: logout
      tags:
      - Auth & session
      summary: Log out and revoke tokens
      description: |-
        **Use case.** Called from the side menu, Settings, the Data download screen and the device-conflict dialog when the user logs out, and after executing a LOGOUT remote action. The app first tries to drain the outbox and warns about unsynced items. The App Service revokes the refresh token, removes the device's push token and, when the tenant enables it, expires the device binding with remark `USER_LOGOUT`. The app then wipes the current user's local data (never another user's outbox).

        **Rules.**
        - Idempotent: logging out twice returns 204.
      x-replaces:
      - PATCH /gateway/urvi-app-service/api/v1/user-device-sessions/users/{userId}?remarks=
      x-features:
      - side-menu
      - settings
      - device-registration
      - data-download-cadre
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LogoutRequest'
            example:
              refreshToken: rt_EXAMPLE_0000000000000000000000
              reason: USER_LOGOUT
      responses:
        '204':
          description: Logged out.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /session/bootstrap:
    get:
      operationId: getBootstrap
      tags:
      - Auth & session
      summary: Session bootstrap after login and on cold start
      description: |-
        **Use case.** Called once right after login and on every cold start (cached; offline the app uses the last copy). Returns the user, tenant, flow type (Cadre / Unit / FCCC), typed permissions, the work-node tree for the Village > Habitation > VO picker, the device-binding status, pending remote actions, app-version policy and the current config ETag. The app routes: Cadre without a saved VO -> work-node selection; Unit -> unit data download; binding `CONFLICT` -> the non-dismissable device-conflict dialog; pending actions -> the remote-action lock screen.

        **Rules.**
        - Unit flow when any role contains `unit-incharge` or `fmt_l1` (tenant `ap.gov`).
        - Device binding: no session -> the server registers this device (`BOUND_HERE`); other device bound -> `CONFLICT` with `otherDevice`.
        - No assigned work nodes -> 200 with an empty `workNodes` array; the app shows its "technical issue" error and offers logout.

        **Error codes.** `DEVICE_BLOCKED`, `VERSION_TOO_OLD`
      x-replaces:
      - GET /rtld/api/dataset/v1/execute?name=PlusOneTenantUserData
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneUrviGeoDataForVOs/execute
      - GET /gateway/urvi-app-service/api/v1/user-device-sessions/user
      - GET /rtld/api/dataset/v1/execute?name=PlusOneUnitInchargeInfo
      x-features:
      - startup-routing
      - work-node-selection
      - device-registration
      - home-navigation
      - side-menu
      x-error-codes:
      - DEVICE_BLOCKED
      - VERSION_TOO_OLD
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      responses:
        '200':
          description: Bootstrap.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Bootstrap'
              example:
                user:
                  userId: 9001
                  login: user.a@@ap.gov
                  displayName: User A
                  roleCodes:
                  - role.ap.gov.cadre
                  flowType: CADRE
                  permissions:
                    farmerAddEdit: true
                    readOnlyProfile: false
                    endorse: false
                    resolveFlags: true
                    compactBlocks: false
                    fmtCropEdit: false
                    voGeoFilter: false
                    analytics: true
                    surveys: false
                  unitIncharge:
                    userId: 9002
                    displayName: User B
                    roleCode: role.ap.gov.unit-incharge
                tenant:
                  code: ap.gov
                  name: RySS
                  languages:
                  - en
                  - te
                  timeZone: Asia/Kolkata
                workNodes:
                - id: 3101
                  code: VIL-3101
                  type: VILLAGE
                  name:
                    en: Village A
                    te: Village A (te)
                  parentId: null
                  children:
                  - id: 3901
                    code: HAB-3901
                    type: HABITATION
                    name:
                      en: Habitation A
                      te: Habitation A (te)
                    parentId: 3101
                    children:
                    - id: 4101
                      code: VO-4101
                      type: VO
                      name:
                        en: VO A
                        te: VO A (te)
                      parentId: 3901
                      children: []
                device:
                  enforced: true
                  status: BOUND_HERE
                  canTakeOver: true
                pendingActions: []
                appVersion:
                  minimum: 2.0.0
                  recommended: 2.1.0
                  latest: 2.1.0
                configEtag: '"cfg-2025-26-v41"'
                serverTime: '2025-10-14T09:30:00+05:30'
                currentSeason: 2025-26
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /me/login-history:
    get:
      operationId: getLoginHistory
      tags:
      - Auth & session
      summary: The signed-in user's login history
      description: '**Use case.** Called online by the Login history screen from the side menu. Shows the last logins of the
        current user (time, device model, app version). Not cached offline beyond the last response.'
      x-replaces:
      - GET /rtld/api/dataset/v1/execute?name=PlusOneCadreLoginHistory
      x-features:
      - login-history
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Login history page.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginHistoryPage'
              example:
                items:
                - loggedInAt: '2025-10-14T09:30:00+05:30'
                  deviceModel: ModelA
                  appVersion: 2.0.0+200
                  outcome: SUCCESS
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 50
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /config:
    get:
      operationId: getConfig
      tags:
      - Config
      summary: Typed config bundle (ETag)
      description: |-
        **Use case.** Called during data download (first item), on Home pull-to-refresh, hourly in the background and whenever bootstrap reports a new `configEtag`. Always sent with `If-None-Match`; usually answers 304. Carries feature switches, the current season, sync knobs, all masters (products, practices, asset categories, UOM, dropdowns, AAP reject reasons), help videos, programme instructions and config, the KAP form schema and every validation limit, so client and server validate with the same numbers. The app replaces its local masters atomically on 200.

        **Rules.**
        - Labels carry every language; the app picks the user's language with English fallback.
        - Replaces `*_EXTERNAL_SWITCH` ref-data, Firebase Remote Config and `versionName_versionCode` allow-lists.
        - Season is `current`; the server maps it to year-specific datasets (no app release per season).

        **Error codes.** `VERSION_TOO_OLD`
      x-replaces:
      - GET /gateway/portfolio-service/api/v1/ref-data/class/CLASS.PORTFOLIO_APP_CONFIG?page&size=100&direction=ASC
      - GET /rtld/api/dataset/v1/execute?name=AAP_REJECT_REASON
      - GET /rtld/api/dataset/v1/execute?name=UrviYouTube
      - GET /rtld/api/dataset/v1/execute?name=PlusOnePortfolioAppConfig
      - GET /rtld/api/dataset/v1/execute?name=PlusOneInstructions
      - GET /ctld/api/product/v1 (generated ProductAPI.getProduct)
      - GET /ie-registration/assets/json/inventory/crops-list.json
      - GET /gateway/portfolio-service/api/v1/practices/active?direction=ASC&page=1&size=100
      - GET S3 igx-static-data/asset/ref_data.json
      - GET S3 igx-static-data/asset/classification.json
      - SDK Firebase Remote Config
      x-features:
      - language-selection
      - startup-routing
      - device-registration
      - data-download-cadre
      - data-download-unit
      - home-navigation
      - farmer-hub
      - farmer-list-search
      - add-edit-farmer-ie-profile
      - farmer-member-category
      - land-assets
      - leased-land
      - geofencing
      - livestock
      - crop-stock-inventory
      - dkg-kitchen-garden
      - atm-model
      - atm-harvest
      - crop-actual-harvest
      - analytics-dashboard
      - crop-actual
      - pmds-a-grade-crop-details
      - certification-survey
      - compact-block
      - activity-tracker
      - kap-25-26
      - vo-shg-farmer-endorsement
      - settings
      - program-configuration-instructions
      x-error-codes:
      - VERSION_TOO_OLD
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Config bundle.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfigBundle'
              example:
                version: cfg-2025-26-v41
                generatedAt: '2025-10-14T09:30:00+05:30'
                season:
                  current: 2025-26
                  previous: 2024-25
                  startDate: '2025-06-01'
                  endDate: '2026-05-31'
                  kapSeason: 2025-26
                featureSwitches:
                  farmerAddEdit: true
                  profileConsent: true
                  multipleCategoryEdits: false
                  editAuthenticatedLand: false
                  khataMandatory: true
                  surveyMandatory: true
                  geofencingAssets: true
                  geofencingAtm: true
                  atm: true
                  atmHarvest: true
                  cropActual: true
                  cropActualHarvestContinuous: true
                  cropActualHarvestWhole: true
                  pmdsCropType: true
                  rdsCropType: false
                  cropActualCopyForward: true
                  copyDisabledCropActual: false
                  currentMonthPortfolioOnly: true
                  kap: true
                  certification: true
                  activityTracker: true
                  profile: true
                  land: true
                  deviceBinding: true
                  removeBindingOnNewDevice: true
                  removeBindingOnLogout: true
                  showForceSync: true
                  showAnalyticsTab: true
                  helpVideoAutoPopup: false
                geofenceMethods:
                  osmFixed: true
                  osmMotion: true
                  osmTap: true
                  googleEarth: false
                  kmlUpload: true
                sync:
                  maxOpsPerBatch: 100
                  retryBaseSeconds: 30
                  retryMaxSeconds: 3600
                  maxAttempts: 20
                  snapshotPageSize: 150
                  setupCacheMinutes: 60
                  disabledLandRefreshMinutes: 360
                  analyticsRefreshHours: 12
                  changesPollMinutes: 15
                formLimits:
                  farmer:
                    nameMaxLength: 100
                    reasonMaxLength: 100
                    ageMin: 18
                    ageMax: 110
                    lastMandatoryCategoryUpdateDate: '2025-07-31'
                  land:
                    acresMin: 0.01
                    acresMax: 99.99
                  livestock:
                    countMax: 999
                  atm:
                    acresMax: 99.99
                    c5MaxAcres: 0.5
                    c5MaxCount: 1
                  cropBlock:
                    acresMax: 99.99
                    sowingLookbackMonths: 6
                  pmds:
                    seedVarietiesMin: 6
                    seedVarietiesMax: 35
                    seedQuantityKgMin: 1
                    seedQuantityKgMax: 999
                  aGrade:
                    mainCropsMax: 1
                    associatedCropsMin: 2
                    biodiversityCropsMin: 2
                  harvest:
                    cropMaxQuintals: 999.99
                  compactBlock:
                    minTotalAcres: 5
                  geofence:
                    minPoints: 3
                    maxAccuracyM: 20
                    motionMinDistanceM: 5
                    motionIntervalSeconds: 2
                  activityTracker:
                    submitAfterLocalTime: '18:00'
                    photoMaxKb: 45
                  kap:
                    extentMax: 99.99
                masters:
                  products:
                  - code: PADDY
                    name:
                      en: Paddy
                      te: Paddy (te)
                    majorType: CEREALS
                    categoryCode: CEREALS
                    cropTypes:
                    - AGRICULTURE
                    - A_GRADE
                    - NO_NF_CROP
                    isDummy: false
                  - code: TOMATO
                    name:
                      en: Tomato
                      te: Tomato (te)
                    majorType: VEGETABLES_LEAFY_TUBERS
                    categoryCode: VEGETABLES
                    cropTypes:
                    - AGRICULTURE
                    - HORTICULTURE
                    isDummy: false
                  practices:
                  - code: AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING
                    name:
                      en: 100% chemical-free natural farming
                      te: 100% chemical-free natural farming (te)
                    isAGrade: false
                    isS2S: true
                    isPmds: false
                    isNoNf: false
                    isChemicalShare: false
                  assetCategories:
                  - code: LAND_OWNED
                    landType: IRRIGATED
                    functionalityCode: IRRIGATED
                    label:
                      en: Land Owned - Irrigated
                      te: Land Owned - Irrigated (te)
                    khataApplicable: true
                    surveyApplicable: true
                    acresApplicable: true
                    descriptionApplicable: false
                  uoms:
                  - code: UOM.AREA.ACRE
                    symbol: ac
                    name:
                      en: Acre
                      te: Acre (te)
                  - code: UOM.WEIGHT.QUINTAL
                    symbol: q
                    name:
                      en: Quintal
                      te: Quintal (te)
                  genders:
                  - code: FEMALE
                    label:
                      en: Female
                      te: Female (te)
                    order: 1
                  - code: MALE
                    label:
                      en: Male
                      te: Male (te)
                    order: 2
                  lifeStatusReasons:
                  - code: MIGRATED
                    label:
                      en: Migrated to another village
                      te: Migrated to another village (te)
                    group: INACTIVE
                    order: 2
                  - code: OTHER
                    label:
                      en: Other
                      te: Other (te)
                    group: INACTIVE
                    order: 9
                    requiresText: true
                  aapRejectReasons:
                  - code: EXTENT_MISMATCH
                    label:
                      en: Extent does not match land records
                      te: Extent does not match land records (te)
                    order: 1
                kapFormSchema:
                  season: 2025-26
                  previousSeason: 2024-25
                  fields:
                  - code: c1Acres
                    label:
                      en: C1 extent (acres)
                      te: C1 extent (acres) (te)
                    type: decimal
                    min: 0
                    max: 99.99
                    enabled: true
                    disabledForCategories:
                    - C4
                    - C5
                  - code: pmdsAcres
                    label:
                      en: PMDS extent (acres)
                      te: PMDS extent (acres) (te)
                    type: decimal
                    min: 0
                    max: 99.99
                    maxOf: c1Acres
                    enabled: true
                    disabledForCategories:
                    - C4
                    - C5
                  pmdsYearOptions:
                  - 2019-20
                  - 2020-21
                  - 2021-22
                  - 2022-23
                  - 2023-24
                  - 2024-25
                  dgcSeasonOptions:
                  - PMDS
                  - KHARIF
                  - RABI
                  - RDSS1
                  - RDSS2
                helpVideos:
                - module: CROP_ACTUAL
                  title:
                    en: How to record Crop Actual
                    te: How to record Crop Actual (te)
                  url: https://video.example.invalid/crop-actual
                  autoPopup: false
                programInstructions:
                - order: 1
                  text:
                    en: Record Crop Actual every month.
                    te: Record Crop Actual every month. (te)
                programConfig:
                - key: KAP_25_26
                  label:
                    en: KAP 25-26 entries
                    te: KAP 25-26 entries (te)
                  value: '1'
                homeBanner:
                  en: Welcome to Urvi
                  te: Welcome to Urvi (te)
                notificationWebUrl: https://notices.example.invalid/urvi
        '304':
          $ref: '#/components/responses/NotModified'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /geo/children:
    get:
      operationId: getGeoChildren
      tags:
      - Geography
      summary: Children of a geography node
      description: |-
        **Use case.** Called online by cascading geography pickers: the leased-land lessor search (District > Mandal > Village > VO > Habitation > SHG), the FCCC VO filter and the endorsement location filter. With no `parent` returns the districts of the tenant. Cached server-side; the app caches per parent for the session.

        **Rules.**
        - `level` must be the child level of `parent` (e.g. parent VO -> HABITATION).
        - SHG lists start with the synthetic `Non-SHG` entry (id 0).

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneGetAllDistricts/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneGetAllMandalsForDistrict/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneGetAllVillagesForMandal/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneGetAllVOForVillage/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneGetAllHabitationForVOAliasId/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneHabitationSHGList/execute
      x-features:
      - leased-land
      - vo-geo-filter
      - farmer-endorsement-details
      - work-node-selection
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: level
        in: query
        required: true
        description: Level of the children requested.
        schema:
          $ref: '#/components/schemas/GeoNodeType'
        example: HABITATION
      - name: parent
        in: query
        required: false
        description: Parent node id; omit for districts.
        schema:
          type: integer
          format: int64
        example: 4101
      responses:
        '200':
          description: Children.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeoChildrenResponse'
              example:
                parent:
                  id: 4101
                  type: VO
                  name: VO A
                  code: VO-4101
                level: HABITATION
                items:
                - id: 3901
                  code: HAB-3901
                  type: HABITATION
                  name:
                    en: Habitation A
                    te: Habitation A (te)
                  parentId: 4101
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/snapshot:
    get:
      operationId: getFarmerSnapshot
      tags:
      - Farmers
      summary: Cursor-paged hydrated farmer snapshot
      description: |-
        **Use case.** Called by Data download (Cadre: the selected VO; Unit / FMT: every assigned VO) and by Settings > Load more data. Each page holds fully hydrated farmers: profile, roles, land with geofences, livestock, crop stock, current-season Crop Actual blocks, farming models (ATM, DKG), harvests, KAP plan, land metrics and endorsement status, with attachment URLs (never bytes). The app writes each page in one transaction and stores the cursor in `sync_checkpoint`, so an interrupted download resumes from the failed page (replaces PARTIAL_SUCCESS bookkeeping). The last page returns `deltaCursor` for `/farmers/changes`.

        **Rules.**
        - `scope` must be one of the user's work nodes (`vo:<id>`, `unit` for every assigned VO, `self` for the user's own record).
        - Zero farmers -> 200 with empty `items` and `totalCount` 0 (the app shows "No farmers found").
        - With `featureSwitches.currentMonthPortfolioOnly`, only Crop Actual blocks of the current and previous month are included.
        - Pages are consistent: the snapshot is taken at the first page; changes after it arrive through `/farmers/changes` from `deltaCursor`.

        **Error codes.** `NOT_FOUND`, `CURSOR_EXPIRED`, `ROLE_NOT_ALLOWED`
      x-replaces:
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneWorkNodeFarmerListGeoWiseCount/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneWorkNodeFarmerListGeoWise/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneWorkNodeFarmerListGeoWiseCountUnit/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneWorkNodeFarmerListGeoWiseUnit/execute
      - GET /ctld/api/ierolemap/v1?criteria=isActive=true AND iE.id in (...)
      - GET /gateway/asset-service/api/v1/assets/ownerEntities?ownerEntityIdList=&ownerEntityType=OWNER_ENTITY.IE
      - GET /gateway/asset-service/api/v1/asset/attachment/bulk?assetIds=
      - GET /gateway/portfolio-service/api/v1/portfolios/ownerEntities?ownerEntityIdList=&ownerEntityType=ENTITY_TYPE.IE
      - GET /gateway/portfolio-service/api/v1/portfolios/line-items/portfoliosForLineItemIds?portfolioLineItemIds=&returnAllMimeType=true
      - GET /gateway/portfolio-service/api/v1/portfolios/attachment/bulk?portfolioAttachmentIds=
      - GET /gateway/portfolio-service/api/v1/farming-model/ownerEntities?ownerEntityIds=&ownerEntityType=ENTITY_TYPE.IE
      - GET /gateway/portfolio-service/api/v1/farming-model/attachment/bulk?farmingModelIds=
      - GET /ctld/api/inventory/inventoryproduct/v1?inventoryId=0&criteria=isActive=true AND inventory.supervisor.actorId
        in (...)
      - GET /gateway/commons-report-service/api/v1/datasets/name/PLUS_ONE_FARMING_CATEGORY_LAST_UPDATE_DATE/execute
      x-features:
      - data-download-cadre
      - data-download-unit
      - farmer-list-search
      - farmer-hub
      - add-edit-farmer-ie-profile
      - farmer-member-category
      - land-assets
      - leased-land
      - geofencing
      - livestock
      - crop-stock-inventory
      - dkg-kitchen-garden
      - atm-model
      - atm-harvest
      - crop-actual
      - crop-actual-harvest
      - pmds-a-grade-crop-details
      - kap-25-26
      - compact-block
      - home-navigation
      x-error-codes:
      - NOT_FOUND
      - CURSOR_EXPIRED
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: scope
        in: query
        required: true
        description: 'Work-node scope: `vo:<id>`, `unit` or `self`.'
        schema:
          type: string
          pattern: ^(vo:\d+|unit|self)$
        example: vo:4101
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Snapshot page.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SnapshotPage'
              example:
                scope: vo:4101
                items:
                - farmer:
                    id: 700001
                    clientId: 3f6c1a2e-0000-4000-8000-000000061441
                    version: 7
                    createdAt: '2025-06-02T10:00:00+05:30'
                    updatedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                    ieId: IE-AP-0000001
                    name: Farmer A
                    gender: FEMALE
                    age: 42
                    mobile: '9000000001'
                    identity:
                      aadhaarMasked: XXXX-XXXX-1234
                      aadhaarSharedWithOther: false
                    maritalStatus: MARRIED
                    family:
                      relation: SPOUSE
                      name: Spouse A
                      identity:
                        aadhaarMasked: XXXX-XXXX-5678
                    education: SECONDARY
                    socialCategory: BC
                    village:
                      id: 3101
                      type: VILLAGE
                      name: Village A
                      code: VIL-3101
                    habitation:
                      id: 3901
                      type: HABITATION
                      name: Habitation A
                      code: HAB-3901
                    vo:
                      id: 4101
                      type: VO
                      name: VO A
                      code: VO-4101
                    shg:
                      id: 52001
                      type: SHG
                      name: SHG A
                      code: SHG-52001
                    roles:
                    - roleCode: MEMBER
                    - roleCode: CADRE
                      cadreDesignation: L1
                    category:
                      code: FARMER
                      setAt: '2025-06-02T10:00:00+05:30'
                      frozen: true
                      frozenReason: LAND_AUTHENTICATED
                    status:
                      lifeStatus: ACTIVE
                    photo:
                      fileId: 7a6b5c4d-0000-4000-8000-000000000225
                      kind: PHOTO_FARMER
                      url: https://files.example.invalid/urvi/signed/farmer-a.jpg
                      mimeType: image/jpeg
                    interestedInTrade: true
                    nfCategory: PMDS+C1
                    endorsementStatus: PENDING
                    flags:
                      hasAtm: true
                      hasAGrade: false
                      hasCompactBlock: true
                      profileComplete: true
                  lands:
                  - id: 810001
                    clientId: 4a7d2b3f-0000-4000-8000-000000004097
                    version: 3
                    createdAt: '2025-06-02T10:05:00+05:30'
                    updatedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    category: LAND_OWNED
                    landType: IRRIGATED
                    ownershipType: OWNER
                    khataNo: KH-0001
                    surveyNo: SY-101/2
                    acres: 2.5
                    authenticated: true
                    disabled: false
                    leasedTo: []
                    geofence:
                      polygon:
                        type: Polygon
                        coordinates:
                        - - - 80.1001
                            - 16.5001
                          - - 80.1012
                            - 16.5001
                          - - 80.1012
                            - 16.501
                          - - 80.1001
                            - 16.501
                          - - 80.1001
                            - 16.5001
                      captureMethod: OSM_MOTION
                      accuracyM: 4.5
                      pointCount: 4
                      capturedAt: '2025-10-14T09:30:00+05:30'
                      areaAcres: 2.4
                      kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                    checksum: c1a5e0d7
                  livestock:
                  - id: 820001
                    clientId: 8e1b6f7d-0000-4000-8000-000000000001
                    version: 1
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    animalType: ADULT_COW
                    count: 2
                    active: true
                  cropStock:
                  - id: 830001
                    clientId: 8e1b6f7d-0000-4000-8000-000000000002
                    version: 1
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    productCode: PADDY
                    quantityQuintals: 12.5
                    active: true
                  cropBlocks:
                  - id: 840001
                    clientId: 5b8e3c4a-0000-4000-8000-000000008193
                    version: 4
                    createdAt: '2025-10-01T08:00:00+05:30'
                    updatedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    landId: 810001
                    landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                    landChecksum: c1a5e0d7
                    tileId: TILE-840001
                    planType: SEASON_ACTUAL
                    season: 2025-26
                    month: 2025-10
                    cropType: AGRICULTURE
                    crops:
                    - clientId: 5b8e3c4a-0000-4000-8000-000000008449
                      id: 841001
                      productCode: PADDY
                      acres: 1.5
                      isMajor: true
                      seedVariety: BPT-5204
                      expectedHarvestDate: '2025-12-15'
                      harvestStatus: NOT_STARTED
                      placeholder: false
                    - clientId: 5b8e3c4a-0000-4000-8000-000000008450
                      id: 841002
                      productCode: RED_GRAM
                      acres: 0.5
                      isMajor: false
                      harvestStatus: NOT_STARTED
                      placeholder: false
                    practiceCodes:
                    - AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING
                    - AP-GOV-KAP-BEEJAMRUTHAM
                    sowingDate: '2025-07-10'
                    photo:
                      fileId: 7a6b5c4d-0000-4000-8000-000000000225
                      kind: PHOTO_CROP
                      url: https://files.example.invalid/urvi/signed/crop-840001.jpg
                    geofence:
                      polygon:
                        type: Polygon
                        coordinates:
                        - - - 80.1001
                            - 16.5001
                          - - 80.1012
                            - 16.5001
                          - - 80.1012
                            - 16.501
                          - - 80.1001
                            - 16.501
                          - - 80.1001
                            - 16.5001
                      captureMethod: OSM_MOTION
                      accuracyM: 4.5
                      pointCount: 4
                      capturedAt: '2025-10-14T09:30:00+05:30'
                      areaAcres: 2.4
                      kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                    changeLog: []
                    compactBlockId: 860001
                    editable: false
                    totalAcres: 2.0
                  farmingModels:
                  - id: 850001
                    clientId: 6c9f4d5b-0000-4000-8000-000000012289
                    version: 2
                    createdAt: '2025-08-01T09:00:00+05:30'
                    updatedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                    modelType: ATM
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    landId: 810001
                    landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                    landChecksum: c1a5e0d7
                    tileId: ATM-850001
                    landAcres: 0.25
                    raisedBeds: true
                    vegetableCrops:
                    - clientId: 6c9f4d5b-0000-4000-8000-000000012545
                      productCode: TOMATO
                      lineType: VEGETABLE
                      harvestStatus: PENDING
                    relayCropping: false
                    maizeCultivation: true
                    pulseCrops:
                    - clientId: 6c9f4d5b-0000-4000-8000-000000012546
                      productCode: GREEN_GRAM
                      lineType: PULSE
                      harvestStatus: PENDING
                    practiceCodes:
                    - AP-GOV-KAP-BEEJAMRUTHAM
                    incomeGeneration: true
                    incomeGenerationDetails: 3500
                    photo:
                      fileId: 7a6b5c4d-0000-4000-8000-000000000225
                      kind: PHOTO_ATM
                      url: https://files.example.invalid/urvi/signed/atm-850001.jpg
                    harvestStatus: PENDING
                  - id: 850002
                    clientId: 6c9f4d5b-0000-4000-8000-000000012290
                    version: 1
                    modelType: DKG_365
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    month: 2025-10
                    continuing: true
                    active: true
                  harvests: []
                  plan:
                    id: 870001
                    clientId: a03d8b9f-0000-4000-8000-000000000001
                    version: 2
                    updatedAt: '2025-10-14T09:30:00+05:30'
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    season: 2025-26
                    status: SUBMITTED
                    lockedReason: null
                    previousEndorsed:
                      c1Acres: 2.0
                      pmdsAcres: 0.5
                      s2sAcres: 1.0
                      aGradeAcres: 0
                      s2sInCompactBlockAcres: 0.5
                      desiCows: 2
                      dkg: SEASONAL
                      atm: true
                    previousCurrent:
                      c1Acres: 2.0
                      pmdsAcres: 0.5
                      s2sAcres: 1.0
                      aGradeAcres: 0
                      s2sInCompactBlockAcres: 0.5
                      desiCows: 2
                      dkg: SEASONAL
                      atm: true
                    current:
                      c1Acres: 2.5
                      pmdsAcres: 1.0
                      s2sAcres: 1.5
                      aGradeAcres: 0.5
                      s2sInCompactBlockAcres: 1.0
                      desiCows: 2
                      dkg: DKG_365
                      atm: true
                    pmdsPractisingYears:
                    - 2022-23
                    - 2023-24
                    dgc:
                      practising: true
                      seasons:
                      - KHARIF
                      - RABI
                    totalLandExtentPrevious: 2.5
                    totalLandExtentCurrent: 2.5
                    submittedAt: '2025-10-14T09:30:00+05:30'
                  metrics:
                    ownedAcres: 2.5
                    leasedInAcres: 0
                    leasedOutAcres: 0.75
                    cropActualAcres: 1.5
                    atmAcres: 0.25
                    usedAcres: 2.5
                    availableAcres: 0
                    kapSurveyCount: 1
                    computedAt: '2025-10-14T09:30:00+05:30'
                    parcels:
                    - landId: 810001
                      landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                      surveyNo: SY-101/2
                      totalAcres: 2.5
                      leasedOutAcres: 0.75
                      cropActualAcres: 1.5
                      atmAcres: 0.25
                      availableAcres: 0
                      disabled: false
                  endorsement: null
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 150
                totalCount: 1
                deltaCursor: chg_eyJ2IjoxMjM0NX0
                generatedAt: '2025-10-14T09:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '410':
          $ref: '#/components/responses/Gone'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/changes:
    get:
      operationId: getFarmerChanges
      tags:
      - Farmers
      summary: 'Delta sync: upserts and tombstones since a cursor'
      description: |-
        **Use case.** Called by the sync worker after every successful batch, on Home open / pull-to-refresh and every `sync.changesPollMinutes` while online. Returns entity-level upserts and tombstones in the user's scope since the cursor (including server-side changes such as land disabled by an admin, season re-labelling and endorsement cascades). The app upserts rows by `clientId`/`serverId`, never overwriting a row with a pending outbox op (it stores the server row and re-applies the pending patch or flags a conflict), deletes tombstoned rows and stores the new cursor.

        **Rules.**
        - Cursor older than the retention window -> 410 `CURSOR_EXPIRED`; the app downloads a fresh snapshot.
        - Farmers leaving the user's scope arrive as `farmer` tombstones with reason `OUT_OF_SCOPE`.

        **Error codes.** `CURSOR_EXPIRED`
      x-replaces:
      - GET /gateway/commons-report-service/api/v1/datasets/name/DisabledAssetCodeForCadre/execute
      x-features:
      - home-navigation
      - farmer-list-search
      - farmer-hub
      - land-assets
      - crop-actual
      - atm-model
      - compact-block
      - kap-25-26
      - settings
      - pending-sync
      x-error-codes:
      - CURSOR_EXPIRED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: cursor
        in: query
        required: true
        description: Cursor from the snapshot's `deltaCursor` or the previous changes page.
        schema:
          type: string
          maxLength: 512
        example: chg_eyJ2IjoxMjM0NX0
      - name: scope
        in: query
        required: false
        description: Same scope as the snapshot; defaults to all the user's scopes.
        schema:
          type: string
          pattern: ^(vo:\d+|unit|self)$
        example: vo:4101
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Changes page.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChangesPage'
              example:
                upserts:
                - entityType: land
                  serverId: 810001
                  clientId: 4a7d2b3f-0000-4000-8000-000000004097
                  farmerId: 700001
                  version: 4
                  updatedAt: '2025-10-14T09:31:02+05:30'
                  entity:
                    id: 810001
                    clientId: 4a7d2b3f-0000-4000-8000-000000004097
                    version: 4
                    createdAt: '2025-06-02T10:05:00+05:30'
                    updatedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    category: LAND_OWNED
                    landType: IRRIGATED
                    ownershipType: OWNER
                    khataNo: KH-0001
                    surveyNo: SY-101/2
                    acres: 2.5
                    authenticated: true
                    disabled: true
                    leasedTo: []
                    geofence:
                      polygon:
                        type: Polygon
                        coordinates:
                        - - - 80.1001
                            - 16.5001
                          - - 80.1012
                            - 16.5001
                          - - 80.1012
                            - 16.501
                          - - 80.1001
                            - 16.501
                          - - 80.1001
                            - 16.5001
                      captureMethod: OSM_MOTION
                      accuracyM: 4.5
                      pointCount: 4
                      capturedAt: '2025-10-14T09:30:00+05:30'
                      areaAcres: 2.4
                      kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                    checksum: c1a5e0d7
                tombstones:
                - entityType: crop_block
                  serverId: 840009
                  clientId: 5b8e3c4a-0000-4000-8000-000000008201
                  deletedAt: '2025-10-14T09:31:02+05:30'
                  reason: DELETED
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 500
                cursor: chg_eyJ2IjoxMjM5OX0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '410':
          $ref: '#/components/responses/Gone'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/{farmerId}:
    get:
      operationId: getFarmer
      tags:
      - Farmers
      summary: One hydrated farmer (get-or-create)
      description: |-
        **Use case.** Called online when the farmer hub opens with missing data ("Please click here to fetch missing data"), when a notification for an unknown farmer is tapped, and on per-farmer refresh. Accepts a server id or the client UUID; when the UUID matches a master record that has no IE yet, the server creates it (replacing the get-or-create POST on open). `include` limits the sections returned.

        **Rules.**
        - Farmers outside the user's work nodes -> 404.
        - `ETag` per farmer; `If-None-Match` -> 304.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - POST /ptld/api/customworknode/v1/worknodeietag/urvi
      - GET /gateway/asset-service/api/v1/assets/{assetId}
      - GET /gateway/portfolio-service/api/v1/portfolios/portfolioLineItems?portfolioLineItems=
      - GET /gateway/portfolio-service/api/v1/portfolios/line-items/ownerEntities/actual/v2?ownerEntityId&ownerEntityType&date
      - GET /gateway/portfolio-service/api/v1/farming-model/ownerEntities/actual?ownerEntityId&ownerEntityType&date&appCreatedAt
      x-features:
      - farmer-hub
      - add-edit-farmer-ie-profile
      - notifications
      - farmer-endorsement-details
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FarmerIdPath'
      - name: include
        in: query
        required: false
        style: form
        explode: false
        description: Sections to include (default all).
        schema:
          type: array
          items:
            type: string
            enum:
            - assets
            - livestock
            - stock
            - portfolio
            - models
            - harvests
            - plan
            - metrics
            - endorsement
        example:
        - assets
        - portfolio
        - models
        - plan
        - metrics
      - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Farmer.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FarmerAggregate'
              example:
                farmer:
                  id: 700001
                  clientId: 3f6c1a2e-0000-4000-8000-000000061441
                  version: 7
                  createdAt: '2025-06-02T10:00:00+05:30'
                  updatedAt: '2025-10-14T09:30:00+05:30'
                  active: true
                  ieId: IE-AP-0000001
                  name: Farmer A
                  gender: FEMALE
                  age: 42
                  mobile: '9000000001'
                  identity:
                    aadhaarMasked: XXXX-XXXX-1234
                    aadhaarSharedWithOther: false
                  maritalStatus: MARRIED
                  family:
                    relation: SPOUSE
                    name: Spouse A
                    identity:
                      aadhaarMasked: XXXX-XXXX-5678
                  education: SECONDARY
                  socialCategory: BC
                  village:
                    id: 3101
                    type: VILLAGE
                    name: Village A
                    code: VIL-3101
                  habitation:
                    id: 3901
                    type: HABITATION
                    name: Habitation A
                    code: HAB-3901
                  vo:
                    id: 4101
                    type: VO
                    name: VO A
                    code: VO-4101
                  shg:
                    id: 52001
                    type: SHG
                    name: SHG A
                    code: SHG-52001
                  roles:
                  - roleCode: MEMBER
                  - roleCode: CADRE
                    cadreDesignation: L1
                  category:
                    code: FARMER
                    setAt: '2025-06-02T10:00:00+05:30'
                    frozen: true
                    frozenReason: LAND_AUTHENTICATED
                  status:
                    lifeStatus: ACTIVE
                  photo:
                    fileId: 7a6b5c4d-0000-4000-8000-000000000225
                    kind: PHOTO_FARMER
                    url: https://files.example.invalid/urvi/signed/farmer-a.jpg
                    mimeType: image/jpeg
                  interestedInTrade: true
                  nfCategory: PMDS+C1
                  endorsementStatus: PENDING
                  flags:
                    hasAtm: true
                    hasAGrade: false
                    hasCompactBlock: true
                    profileComplete: true
                lands:
                - id: 810001
                  clientId: 4a7d2b3f-0000-4000-8000-000000004097
                  version: 3
                  createdAt: '2025-06-02T10:05:00+05:30'
                  updatedAt: '2025-10-14T09:30:00+05:30'
                  active: true
                  farmerId: 700001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  category: LAND_OWNED
                  landType: IRRIGATED
                  ownershipType: OWNER
                  khataNo: KH-0001
                  surveyNo: SY-101/2
                  acres: 2.5
                  authenticated: true
                  disabled: false
                  leasedTo: []
                  geofence:
                    polygon:
                      type: Polygon
                      coordinates:
                      - - - 80.1001
                          - 16.5001
                        - - 80.1012
                          - 16.5001
                        - - 80.1012
                          - 16.501
                        - - 80.1001
                          - 16.501
                        - - 80.1001
                          - 16.5001
                    captureMethod: OSM_MOTION
                    accuracyM: 4.5
                    pointCount: 4
                    capturedAt: '2025-10-14T09:30:00+05:30'
                    areaAcres: 2.4
                    kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                  checksum: c1a5e0d7
                livestock:
                - id: 820001
                  clientId: 8e1b6f7d-0000-4000-8000-000000000001
                  version: 1
                  farmerId: 700001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  animalType: ADULT_COW
                  count: 2
                  active: true
                cropStock:
                - id: 830001
                  clientId: 8e1b6f7d-0000-4000-8000-000000000002
                  version: 1
                  farmerId: 700001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  productCode: PADDY
                  quantityQuintals: 12.5
                  active: true
                cropBlocks:
                - id: 840001
                  clientId: 5b8e3c4a-0000-4000-8000-000000008193
                  version: 4
                  createdAt: '2025-10-01T08:00:00+05:30'
                  updatedAt: '2025-10-14T09:30:00+05:30'
                  active: true
                  farmerId: 700001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  landId: 810001
                  landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                  landChecksum: c1a5e0d7
                  tileId: TILE-840001
                  planType: SEASON_ACTUAL
                  season: 2025-26
                  month: 2025-10
                  cropType: AGRICULTURE
                  crops:
                  - clientId: 5b8e3c4a-0000-4000-8000-000000008449
                    id: 841001
                    productCode: PADDY
                    acres: 1.5
                    isMajor: true
                    seedVariety: BPT-5204
                    expectedHarvestDate: '2025-12-15'
                    harvestStatus: NOT_STARTED
                    placeholder: false
                  - clientId: 5b8e3c4a-0000-4000-8000-000000008450
                    id: 841002
                    productCode: RED_GRAM
                    acres: 0.5
                    isMajor: false
                    harvestStatus: NOT_STARTED
                    placeholder: false
                  practiceCodes:
                  - AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING
                  - AP-GOV-KAP-BEEJAMRUTHAM
                  sowingDate: '2025-07-10'
                  photo:
                    fileId: 7a6b5c4d-0000-4000-8000-000000000225
                    kind: PHOTO_CROP
                    url: https://files.example.invalid/urvi/signed/crop-840001.jpg
                  geofence:
                    polygon:
                      type: Polygon
                      coordinates:
                      - - - 80.1001
                          - 16.5001
                        - - 80.1012
                          - 16.5001
                        - - 80.1012
                          - 16.501
                        - - 80.1001
                          - 16.501
                        - - 80.1001
                          - 16.5001
                    captureMethod: OSM_MOTION
                    accuracyM: 4.5
                    pointCount: 4
                    capturedAt: '2025-10-14T09:30:00+05:30'
                    areaAcres: 2.4
                    kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                  changeLog: []
                  compactBlockId: 860001
                  editable: false
                  totalAcres: 2.0
                farmingModels:
                - id: 850001
                  clientId: 6c9f4d5b-0000-4000-8000-000000012289
                  version: 2
                  createdAt: '2025-08-01T09:00:00+05:30'
                  updatedAt: '2025-10-14T09:30:00+05:30'
                  active: true
                  modelType: ATM
                  farmerId: 700001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  landId: 810001
                  landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                  landChecksum: c1a5e0d7
                  tileId: ATM-850001
                  landAcres: 0.25
                  raisedBeds: true
                  vegetableCrops:
                  - clientId: 6c9f4d5b-0000-4000-8000-000000012545
                    productCode: TOMATO
                    lineType: VEGETABLE
                    harvestStatus: PENDING
                  relayCropping: false
                  maizeCultivation: true
                  pulseCrops:
                  - clientId: 6c9f4d5b-0000-4000-8000-000000012546
                    productCode: GREEN_GRAM
                    lineType: PULSE
                    harvestStatus: PENDING
                  practiceCodes:
                  - AP-GOV-KAP-BEEJAMRUTHAM
                  incomeGeneration: true
                  incomeGenerationDetails: 3500
                  photo:
                    fileId: 7a6b5c4d-0000-4000-8000-000000000225
                    kind: PHOTO_ATM
                    url: https://files.example.invalid/urvi/signed/atm-850001.jpg
                  harvestStatus: PENDING
                - id: 850002
                  clientId: 6c9f4d5b-0000-4000-8000-000000012290
                  version: 1
                  modelType: DKG_365
                  farmerId: 700001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  month: 2025-10
                  continuing: true
                  active: true
                harvests: []
                plan:
                  id: 870001
                  clientId: a03d8b9f-0000-4000-8000-000000000001
                  version: 2
                  updatedAt: '2025-10-14T09:30:00+05:30'
                  farmerId: 700001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  season: 2025-26
                  status: SUBMITTED
                  lockedReason: null
                  previousEndorsed:
                    c1Acres: 2.0
                    pmdsAcres: 0.5
                    s2sAcres: 1.0
                    aGradeAcres: 0
                    s2sInCompactBlockAcres: 0.5
                    desiCows: 2
                    dkg: SEASONAL
                    atm: true
                  previousCurrent:
                    c1Acres: 2.0
                    pmdsAcres: 0.5
                    s2sAcres: 1.0
                    aGradeAcres: 0
                    s2sInCompactBlockAcres: 0.5
                    desiCows: 2
                    dkg: SEASONAL
                    atm: true
                  current:
                    c1Acres: 2.5
                    pmdsAcres: 1.0
                    s2sAcres: 1.5
                    aGradeAcres: 0.5
                    s2sInCompactBlockAcres: 1.0
                    desiCows: 2
                    dkg: DKG_365
                    atm: true
                  pmdsPractisingYears:
                  - 2022-23
                  - 2023-24
                  dgc:
                    practising: true
                    seasons:
                    - KHARIF
                    - RABI
                  totalLandExtentPrevious: 2.5
                  totalLandExtentCurrent: 2.5
                  submittedAt: '2025-10-14T09:30:00+05:30'
                metrics:
                  ownedAcres: 2.5
                  leasedInAcres: 0
                  leasedOutAcres: 0.75
                  cropActualAcres: 1.5
                  atmAcres: 0.25
                  usedAcres: 2.5
                  availableAcres: 0
                  kapSurveyCount: 1
                  computedAt: '2025-10-14T09:30:00+05:30'
                  parcels:
                  - landId: 810001
                    landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                    surveyNo: SY-101/2
                    totalAcres: 2.5
                    leasedOutAcres: 0.75
                    cropActualAcres: 1.5
                    atmAcres: 0.25
                    availableAcres: 0
                    disabled: false
                endorsement: null
        '304':
          $ref: '#/components/responses/NotModified'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/search:
    get:
      operationId: searchFarmers
      tags:
      - Farmers
      summary: Search farmers in a geography (online)
      description: |-
        **Use case.** Called online by the leased-land lessor picker (farmers of an SHG, or non-SHG farmers of a VO), the FCCC habitation filter and the endorsement farmer lists (with `kapSubmitted` for bulk endorse). Read-only: never wipes or replaces local data (the FCCC flow stores results in a separate cache).

        **Rules.**
        - `geo` must be inside the user's work nodes or, for FCCC users, inside the tenant.
        - `q` all digits starting 6-9 searches mobile numbers; otherwise names (prefix matches first).

        **Error codes.** `ROLE_NOT_ALLOWED`
      x-replaces:
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneWorkNodeFarmerHusbandFatherSHGWise/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneWorkNodeFarmerHusbandFatherVOWiseNonSHG/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneWorkNodeFarmerListGeoWiseNoPagination/execute
      x-features:
      - leased-land
      - vo-geo-filter
      - farmer-endorsement-details
      - vo-shg-farmer-endorsement
      x-error-codes:
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: geo
        in: query
        required: true
        description: 'Geography node: `shg:<id>`, `vo:<id>`, `vo:<id>:non-shg` or `habitation:<id>`.'
        schema:
          type: string
          pattern: ^(shg|vo|habitation):\d+(:non-shg)?$
        example: shg:52001
      - name: q
        in: query
        required: false
        description: Name or mobile prefix.
        schema:
          type: string
          maxLength: 100
        example: Farmer
      - name: excludeFarmerId
        in: query
        required: false
        description: Exclude this farmer (the lessee).
        schema:
          type: integer
          format: int64
        example: 700002
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Matching farmers.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FarmerSearchPage'
              example:
                items:
                - id: 700001
                  clientId: 3f6c1a2e-0000-4000-8000-000000061441
                  ieId: IE-AP-0000001
                  name: Farmer A
                  relativeName: Spouse A
                  gender: FEMALE
                  shg:
                    id: 52001
                    type: SHG
                    name: SHG A
                    code: SHG-52001
                  vo:
                    id: 4101
                    type: VO
                    name: VO A
                    code: VO-4101
                  category: FARMER
                  lifeStatus: ACTIVE
                  kapSubmitted: true
                  endorsementStatus: PENDING
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 100
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/{farmerId}/abstract:
    get:
      operationId: getFarmerAbstract
      tags:
      - Farmers
      summary: Farmer abstract for a season
      description: |-
        **Use case.** Called online from the farmer hub and the endorsement farmer view to show the farmer's programme abstract (endorsed figures) for a season. The server handles the IE-id-then-UUID fallback and year-specific datasets.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /rtld/api/dataset/v1/execute?name=PlusOneMasterFarmerIEID2425
      - GET /rtld/api/dataset/v1/execute?name=PlusOneMasterFarmerIEUUID2425
      x-features:
      - farmer-hub
      - farmer-endorsement-details
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FarmerIdPath'
      - $ref: '#/components/parameters/Season'
      responses:
        '200':
          description: Abstract.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FarmerAbstract'
              example:
                farmerId: 700001
                season: 2024-25
                fields:
                - key: c1Acres
                  label:
                    en: C1 extent
                    te: C1 extent (te)
                  value: '2.0'
                  unit: acres
                asOf: '2025-10-14T09:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/{farmerId}/lands:
    get:
      operationId: listFarmerLands
      tags:
      - Land & geofence
      summary: A farmer's land parcels
      description: |-
        **Use case.** Called online by the Land screen's pull-to-refresh and after a land-related rejection, to rebuild the local land list with server state (authenticated, disabled, lease status, geofence KML URLs). Leased-in parcels carry `lease`; owned parcels list `leasedTo`.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /gateway/asset-service/api/v1/assets/ownerEntities?ownerEntityIdList=&ownerEntityType=OWNER_ENTITY.IE
      - GET /gateway/asset-service/api/v1/asset/attachment/bulk?assetIds=
      - GET /gateway/asset-service/api/v1/assets/{assetId}
      x-features:
      - land-assets
      - geofencing
      - leased-land
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FarmerIdPath'
      - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Lands.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Land'
              example:
              - id: 810001
                clientId: 4a7d2b3f-0000-4000-8000-000000004097
                version: 3
                createdAt: '2025-06-02T10:05:00+05:30'
                updatedAt: '2025-10-14T09:30:00+05:30'
                active: true
                farmerId: 700001
                farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                category: LAND_OWNED
                landType: IRRIGATED
                ownershipType: OWNER
                khataNo: KH-0001
                surveyNo: SY-101/2
                acres: 2.5
                authenticated: true
                disabled: false
                leasedTo: []
                geofence:
                  polygon:
                    type: Polygon
                    coordinates:
                    - - - 80.1001
                        - 16.5001
                      - - 80.1012
                        - 16.5001
                      - - 80.1012
                        - 16.501
                      - - 80.1001
                        - 16.501
                      - - 80.1001
                        - 16.5001
                  captureMethod: OSM_MOTION
                  accuracyM: 4.5
                  pointCount: 4
                  capturedAt: '2025-10-14T09:30:00+05:30'
                  areaAcres: 2.4
                  kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                checksum: c1a5e0d7
        '304':
          $ref: '#/components/responses/NotModified'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/{farmerId}/metrics:
    get:
      operationId: getFarmerMetrics
      tags:
      - Land & geofence
      summary: Owned, leased, used and available acres
      description: |-
        **Use case.** Called online by the Land edit, Leased land (for the lessor's parcels), ATM, Crop Actual and KAP screens to show available acres. Uses exactly the formula the batch validation applies: available = owned + leased-in - leased-out - current-season Crop Actual (unharvested, latest per tile) - latest ATM sizes. Offline the app computes the same figure locally.

        **Rules.**
        - Values in acres, 2 decimals.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneGetOwnedLandByIEID/execute
      - GET /rtld/api/dataset/v1/execute?name=PlusOneGetATMNFUsedAcreageByIEID
      x-features:
      - land-assets
      - leased-land
      - atm-model
      - crop-actual
      - kap-25-26
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FarmerIdPath'
      - $ref: '#/components/parameters/Season'
      responses:
        '200':
          description: Metrics.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FarmerMetrics'
              example:
                ownedAcres: 2.5
                leasedInAcres: 0
                leasedOutAcres: 0.75
                cropActualAcres: 1.5
                atmAcres: 0.25
                usedAcres: 2.5
                availableAcres: 0
                kapSurveyCount: 1
                computedAt: '2025-10-14T09:30:00+05:30'
                parcels:
                - landId: 810001
                  landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                  surveyNo: SY-101/2
                  totalAcres: 2.5
                  leasedOutAcres: 0.75
                  cropActualAcres: 1.5
                  atmAcres: 0.25
                  availableAcres: 0
                  disabled: false
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/{farmerId}/crop-blocks:
    get:
      operationId: listFarmerCropBlocks
      tags:
      - Crops & harvest
      summary: A farmer's Crop Actual blocks for a season / month
      description: |-
        **Use case.** Called online by Crop Actual on swipe-to-refresh (when the farmer is synced and the interval has passed), after a `PART_OF_COMPACT_BLOCK` rejection, and as the online source for month copy-forward (last month's blocks). Returns blocks with crops, practices, PMDS / A Grade details, photo and geofence URLs, harvest status and compact-block link.

        **Rules.**
        - Only the latest version per `tileId` unless `allVersions=true`.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /gateway/portfolio-service/api/v1/portfolios/portfolioLineItems?portfolioLineItems=
      - GET /gateway/portfolio-service/api/v1/portfolios/line-items/ownerEntities/actual/v2?ownerEntityId&ownerEntityType&date
      - GET /gateway/portfolio-service/api/v1/portfolios/line-items/portfoliosForLineItemIds?portfolioLineItemIds=&returnAllMimeType=true
      x-features:
      - crop-actual
      - crop-actual-harvest
      - pmds-a-grade-crop-details
      - compact-block
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FarmerIdPath'
      - $ref: '#/components/parameters/Season'
      - name: month
        in: query
        required: false
        description: '`yyyy-MM`; default current month.'
        schema:
          type: string
          pattern: ^\d{4}-(0[1-9]|1[0-2])$
        example: 2025-10
      - name: allVersions
        in: query
        required: false
        description: Include every monthly version.
        schema:
          type: boolean
          default: false
        example: false
      responses:
        '200':
          description: Crop blocks.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CropBlock'
              example:
              - id: 840001
                clientId: 5b8e3c4a-0000-4000-8000-000000008193
                version: 4
                createdAt: '2025-10-01T08:00:00+05:30'
                updatedAt: '2025-10-14T09:30:00+05:30'
                active: true
                farmerId: 700001
                farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                landId: 810001
                landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                landChecksum: c1a5e0d7
                tileId: TILE-840001
                planType: SEASON_ACTUAL
                season: 2025-26
                month: 2025-10
                cropType: AGRICULTURE
                crops:
                - clientId: 5b8e3c4a-0000-4000-8000-000000008449
                  id: 841001
                  productCode: PADDY
                  acres: 1.5
                  isMajor: true
                  seedVariety: BPT-5204
                  expectedHarvestDate: '2025-12-15'
                  harvestStatus: NOT_STARTED
                  placeholder: false
                - clientId: 5b8e3c4a-0000-4000-8000-000000008450
                  id: 841002
                  productCode: RED_GRAM
                  acres: 0.5
                  isMajor: false
                  harvestStatus: NOT_STARTED
                  placeholder: false
                practiceCodes:
                - AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING
                - AP-GOV-KAP-BEEJAMRUTHAM
                sowingDate: '2025-07-10'
                photo:
                  fileId: 7a6b5c4d-0000-4000-8000-000000000225
                  kind: PHOTO_CROP
                  url: https://files.example.invalid/urvi/signed/crop-840001.jpg
                geofence:
                  polygon:
                    type: Polygon
                    coordinates:
                    - - - 80.1001
                        - 16.5001
                      - - 80.1012
                        - 16.5001
                      - - 80.1012
                        - 16.501
                      - - 80.1001
                        - 16.501
                      - - 80.1001
                        - 16.5001
                  captureMethod: OSM_MOTION
                  accuracyM: 4.5
                  pointCount: 4
                  capturedAt: '2025-10-14T09:30:00+05:30'
                  areaAcres: 2.4
                  kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                changeLog: []
                compactBlockId: 860001
                editable: false
                totalAcres: 2.0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/{farmerId}/farming-models:
    get:
      operationId: listFarmerFarmingModels
      tags:
      - Farming models
      summary: A farmer's ATM plots and DKG answers
      description: |-
        **Use case.** Called online by the ATM and 365 DKG screens on refresh. Returns the latest version per ATM `tileId` (with crop lines and harvest status) and DKG monthly answers, with photo and geofence URLs.

        **Rules.**
        - ATMs whose every line is COMPLETED are returned with `harvestStatus` COMPLETED; the app hides them.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /gateway/portfolio-service/api/v1/farming-model/ownerEntities/actual?ownerEntityId&ownerEntityType&date&appCreatedAt
      - GET /gateway/portfolio-service/api/v1/farming-model/attachment/bulk?farmingModelIds=
      x-features:
      - atm-model
      - atm-harvest
      - dkg-kitchen-garden
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FarmerIdPath'
      - name: modelType
        in: query
        required: false
        description: Filter by type.
        schema:
          type: string
          enum:
          - ATM
          - DKG_365
        example: ATM
      responses:
        '200':
          description: Farming models.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FarmingModel'
              example:
              - id: 850001
                clientId: 6c9f4d5b-0000-4000-8000-000000012289
                version: 2
                createdAt: '2025-08-01T09:00:00+05:30'
                updatedAt: '2025-10-14T09:30:00+05:30'
                active: true
                modelType: ATM
                farmerId: 700001
                farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                landId: 810001
                landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                landChecksum: c1a5e0d7
                tileId: ATM-850001
                landAcres: 0.25
                raisedBeds: true
                vegetableCrops:
                - clientId: 6c9f4d5b-0000-4000-8000-000000012545
                  productCode: TOMATO
                  lineType: VEGETABLE
                  harvestStatus: PENDING
                relayCropping: false
                maizeCultivation: true
                pulseCrops:
                - clientId: 6c9f4d5b-0000-4000-8000-000000012546
                  productCode: GREEN_GRAM
                  lineType: PULSE
                  harvestStatus: PENDING
                practiceCodes:
                - AP-GOV-KAP-BEEJAMRUTHAM
                incomeGeneration: true
                incomeGenerationDetails: 3500
                photo:
                  fileId: 7a6b5c4d-0000-4000-8000-000000000225
                  kind: PHOTO_ATM
                  url: https://files.example.invalid/urvi/signed/atm-850001.jpg
                harvestStatus: PENDING
              - id: 850002
                clientId: 6c9f4d5b-0000-4000-8000-000000012290
                version: 1
                modelType: DKG_365
                farmerId: 700001
                farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                month: 2025-10
                continuing: true
                active: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /farmers/{farmerId}/plan:
    get:
      operationId: getFarmerPlan
      tags:
      - Planning (KAP)
      summary: KAP / annual action plan for a season
      description: |-
        **Use case.** Called online when KAP 25-26 opens (and read-only from the endorsement farmer list) to load the current-season plan with last season's endorsed and current values and the computed land extents. Submissions go through `/sync/batch` (`kap_plan`). `status` LOCKED with `lockedReason` tells the app to disable the form and show the endorsed banner.

        **Rules.**
        - `season=current` maps to the KAP season in config.

        **Error codes.** `NOT_FOUND`, `CATEGORY_NOT_ALLOWED`
      x-replaces:
      - GET /gateway/commons-report-service/api/v1/datasets/name/AAP2025_26/execute
      - GET /gateway/asset-service/api/v1/annual-action-plan-extended?ownerEntityIds=&ownerEntityType=ENTITY_TYPE.IE
      - GET /rtld/api/dataset/v1/execute?name=PlusOneAAP2526IEIDStatus
      x-features:
      - kap-25-26
      - farmer-endorsement-details
      - vo-shg-farmer-endorsement
      x-error-codes:
      - NOT_FOUND
      - CATEGORY_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FarmerIdPath'
      - $ref: '#/components/parameters/Season'
      responses:
        '200':
          description: Plan.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KapPlan'
              example:
                id: 870001
                clientId: a03d8b9f-0000-4000-8000-000000000001
                version: 2
                updatedAt: '2025-10-14T09:30:00+05:30'
                farmerId: 700001
                farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                season: 2025-26
                status: SUBMITTED
                lockedReason: null
                previousEndorsed:
                  c1Acres: 2.0
                  pmdsAcres: 0.5
                  s2sAcres: 1.0
                  aGradeAcres: 0
                  s2sInCompactBlockAcres: 0.5
                  desiCows: 2
                  dkg: SEASONAL
                  atm: true
                previousCurrent:
                  c1Acres: 2.0
                  pmdsAcres: 0.5
                  s2sAcres: 1.0
                  aGradeAcres: 0
                  s2sInCompactBlockAcres: 0.5
                  desiCows: 2
                  dkg: SEASONAL
                  atm: true
                current:
                  c1Acres: 2.5
                  pmdsAcres: 1.0
                  s2sAcres: 1.5
                  aGradeAcres: 0.5
                  s2sInCompactBlockAcres: 1.0
                  desiCows: 2
                  dkg: DKG_365
                  atm: true
                pmdsPractisingYears:
                - 2022-23
                - 2023-24
                dgc:
                  practising: true
                  seasons:
                  - KHARIF
                  - RABI
                totalLandExtentPrevious: 2.5
                totalLandExtentCurrent: 2.5
                submittedAt: '2025-10-14T09:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /endorsements:
    get:
      operationId: listEndorsements
      tags:
      - Endorsements
      summary: Endorsement statuses (VO, SHG, farmer)
      description: |-
        **Use case.** Called by the endorsement worker every 15 minutes while online, on Home pull-to-refresh, and by the VO tab (Unit). `scope=me` returns endorsements assigned to or made by the user; `scope=vo` returns the VO, its SHGs and farmers in one shape. Each row carries `allowedActions` so the menus (Raise flag, Mark as endorsed, comments, abstracts) come from the server state machine.

        **Error codes.** `ROLE_NOT_ALLOWED`
      x-replaces:
      - GET /gateway/asset-service/api/v1/endorsement/me?page&size
      - GET /rtld/api/dataset/v1/execute?name=PlusOneEndorsementVOHierarchy
      - GET /gateway/asset-service/api/v1/endorsements/attachment?endorsementId&attachmentFormat=ATTACHMENT_URL
      x-features:
      - farmer-endorsement-details
      - vo-shg-farmer-endorsement
      - farmer-list-search
      - home-navigation
      x-error-codes:
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: scope
        in: query
        required: true
        description: '`me` or `vo`.'
        schema:
          type: string
          enum:
          - me
          - vo
        example: vo
      - name: voId
        in: query
        required: false
        description: Required with `scope=vo`.
        schema:
          type: integer
          format: int64
        example: 4101
      - name: since
        in: query
        required: false
        description: Only rows changed after this time.
        schema:
          type: string
          format: date-time
        example: '2025-10-14T09:30:00+05:30'
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Endorsements.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndorsementPage'
              example:
                items:
                - id: 880001
                  entityType: SHG
                  entityId: 52001
                  entityCode: SHG-52001
                  name: SHG A
                  status: FLAGGED
                  vo:
                    id: 4101
                    type: VO
                    name: VO A
                    code: VO-4101
                  shg:
                    id: 52001
                    type: SHG
                    name: SHG A
                    code: SHG-52001
                  reviewedBy:
                    userId: 9001
                    displayName: User A
                    roleCode: role.ap.gov.unit-incharge
                  reviewedAt: '2025-10-14T09:30:00+05:30'
                  allowedActions:
                  - ENDORSE
                  - NOT_RESOLVED
                  commentCount: 2
                  version: 3
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 100
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /endorsements/{entityType}/{entityId}/comments:
    get:
      operationId: listEndorsementComments
      tags:
      - Endorsements
      summary: Comment thread of an endorsed entity
      description: |-
        **Use case.** Called online by the comments sheet (VO / SHG / farmer) with load-more paging. New comments are posted through `/sync/batch` (`endorsement_comment`); auto comments from Not resolved / Mark as resolved appear as `SYSTEM`.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /gateway/asset-service/api/v1/endorsementComment?entityID&entityType&page&size
      x-features:
      - farmer-endorsement-details
      - vo-shg-farmer-endorsement
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/EndorsementEntityTypePath'
      - $ref: '#/components/parameters/EndorsementEntityIdPath'
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Comments.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndorsementCommentPage'
              example:
                items:
                - id: 890001
                  clientId: b14e9caf-0000-4000-8000-000000000002
                  version: 1
                  entityType: SHG
                  entityId: 52001
                  text: Please recheck PMDS extent
                  commentType: USER
                  postedBy:
                    userId: 9001
                    displayName: User A
                    roleCode: role.ap.gov.unit-incharge
                  postedAt: '2025-10-14T09:30:00+05:30'
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 20
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /endorsements/{entityType}/{entityId}/eligibility:
    get:
      operationId: getEndorsementEligibility
      tags:
      - Endorsements
      summary: Why an endorsement action is allowed or blocked
      description: |-
        **Use case.** Called online when the user opens the endorse action, so the UI can explain why Endorse is blocked and list the farmers or SHGs to fix. It evaluates exactly the rules the batch applies to `endorsement_action`; offline the app shows its local preview and the batch remains authoritative.

        **Rules.**
        - Farmer: needs an IE id (`ENDORSEMENT_FARMER_NOT_REGISTERED`) and a frozen category (`ENDORSEMENT_CATEGORY_NOT_FROZEN`); C4 is endorsed directly; others need KAP submitted (`ENDORSEMENT_KAP_NOT_SUBMITTED`).
        - SHG: every active farmer registered and frozen; all-C4 SHGs endorse directly; otherwise every Farmer/C5 member needs KAP submitted.
        - VO: every SHG must already be endorsed (`ENDORSEMENT_ORDER`).
        - Transition not allowed from the current status -> `ENDORSEMENT_TRANSITION_INVALID`.

        **Error codes.** `ENDORSEMENT_ORDER`, `ENDORSEMENT_KAP_NOT_SUBMITTED`, `ENDORSEMENT_CATEGORY_NOT_FROZEN`, `ENDORSEMENT_FARMER_NOT_REGISTERED`, `ENDORSEMENT_TRANSITION_INVALID`
      x-replaces:
      - GET /rtld/api/dataset/v1/execute?name=PlusOneAAP2526IEIDStatus
      x-features:
      - farmer-endorsement-details
      - vo-shg-farmer-endorsement
      x-error-codes:
      - ENDORSEMENT_ORDER
      - ENDORSEMENT_KAP_NOT_SUBMITTED
      - ENDORSEMENT_CATEGORY_NOT_FROZEN
      - ENDORSEMENT_FARMER_NOT_REGISTERED
      - ENDORSEMENT_TRANSITION_INVALID
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/EndorsementEntityTypePath'
      - $ref: '#/components/parameters/EndorsementEntityIdPath'
      - name: action
        in: query
        required: false
        description: Action to evaluate.
        schema:
          $ref: '#/components/schemas/EndorsementActionType'
        example: ENDORSE
      responses:
        '200':
          description: Eligibility.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndorsementEligibility'
              example:
                entityType: SHG
                entityId: 52001
                action: ENDORSE
                eligible: false
                evaluatedAt: '2025-10-14T09:30:00+05:30'
                blockers:
                - code: ENDORSEMENT_KAP_NOT_SUBMITTED
                  message: These members have not submitted KAP.
                  farmers:
                  - id: 700002
                    clientId: 3f6c1a2e-0000-4000-8000-000000061442
                    ieId: IE-AP-0000002
                    name: Farmer B
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /vo/{voId}/abstract:
    get:
      operationId: getVoAbstract
      tags:
      - Endorsements
      summary: VO abstract download link
      description: |-
        **Use case.** Called online from the VO row menu (Download abstract / Get endorsed abstract). Returns a short-lived signed URL of the VO abstract (Excel) or, with `variant=endorsed`, the uploaded endorsed abstract; the app downloads it with the system download manager.

        **Rules.**
        - Not available for farmers.

        **Error codes.** `NOT_FOUND`, `ROLE_NOT_ALLOWED`
      x-replaces:
      - GET /raw-data-service/report/ryss-abstract/{voId}/vo?adminUser=true
      - GET /gateway/asset-service/api/v1/endorsements/attachment?endorsementId&attachmentFormat=ATTACHMENT_URL
      x-features:
      - vo-shg-farmer-endorsement
      x-error-codes:
      - NOT_FOUND
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/VoIdPath'
      - name: variant
        in: query
        required: false
        description: '`generated` (default) or `endorsed`.'
        schema:
          type: string
          enum:
          - generated
          - endorsed
          default: generated
        example: generated
      responses:
        '200':
          description: Download link.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbstractFile'
              example:
                url: https://files.example.invalid/urvi/signed/vo-4101-abstract.xlsx
                fileName: vo-4101-abstract.xlsx
                mimeType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
                expiresAt: '2025-10-14T10:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /shg/{shgId}/abstract:
    get:
      operationId: getShgAbstract
      tags:
      - Endorsements
      summary: SHG abstract download link
      description: |-
        **Use case.** Called online from the SHG row menu (Download abstract / Get endorsed abstract). Returns a short-lived signed URL of the SHG abstract (Excel) or, with `variant=endorsed`, the uploaded endorsed abstract; the app downloads it with the system download manager.

        **Rules.**
        - Not available for farmers.

        **Error codes.** `NOT_FOUND`, `ROLE_NOT_ALLOWED`
      x-replaces:
      - GET /raw-data-service/report/ryss-abstract/{id}/shg
      x-features:
      - vo-shg-farmer-endorsement
      x-error-codes:
      - NOT_FOUND
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/ShgIdPath'
      - name: variant
        in: query
        required: false
        description: '`generated` (default) or `endorsed`.'
        schema:
          type: string
          enum:
          - generated
          - endorsed
          default: generated
        example: generated
      responses:
        '200':
          description: Download link.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbstractFile'
              example:
                url: https://files.example.invalid/urvi/signed/vo-4101-abstract.xlsx
                fileName: vo-4101-abstract.xlsx
                mimeType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
                expiresAt: '2025-10-14T10:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /compact-blocks:
    get:
      operationId: listCompactBlocks
      tags:
      - Compact blocks
      summary: Compact blocks for VOs
      description: |-
        **Use case.** Called by Unit / FMT data download (Compact Block item) and the Compact Block tab refresh. Returns active blocks of the given VOs with members, computed total acres and warning flags (`BELOW_5_ACRES`, `INACTIVE_FARMER`, `NOT_FARMER`, `NOT_S2S`). No hard-coded date cut-off. Writes go through `/sync/batch`.

        **Error codes.** `ROLE_NOT_ALLOWED`
      x-replaces:
      - GET /gateway/portfolio-service/api/v1/compact-block/filter?cbmOwningEntityIds=
      - GET /gateway/portfolio-service/api/v1/compact-block?direction=desc&page&size&voIds=
      x-features:
      - compact-block
      - data-download-unit
      x-error-codes:
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: voIds
        in: query
        required: false
        style: form
        explode: false
        description: VO ids; default all assigned VOs.
        schema:
          type: array
          items:
            type: integer
            format: int64
          maxItems: 200
        example:
        - 4101
        - 4102
      - name: farmerIds
        in: query
        required: false
        style: form
        explode: false
        description: Blocks containing these farmers.
        schema:
          type: array
          items:
            type: integer
            format: int64
          maxItems: 200
        example:
        - 700001
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Compact blocks.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompactBlockPage'
              example:
                items:
                - id: 860001
                  clientId: 7d0a5e6c-0000-4000-8000-000000016385
                  version: 5
                  updatedAt: '2025-10-14T09:30:00+05:30'
                  active: true
                  name: CB 1
                  tileId: CB-860001
                  voIds:
                  - 4101
                  members:
                  - id: 861001
                    clientId: 7d0a5e6c-0000-4000-8000-000000016641
                    compactBlockId: 860001
                    cropBlockId: 840001
                    cropBlockClientId: 5b8e3c4a-0000-4000-8000-000000008193
                    tileId: TILE-840001
                    farmerId: 700001
                    landId: 810001
                    shg:
                      id: 52001
                      type: SHG
                      name: SHG A
                      code: SHG-52001
                    vo:
                      id: 4101
                      type: VO
                      name: VO A
                      code: VO-4101
                    acres: 2.0
                    joinedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                  - id: 861002
                    clientId: 7d0a5e6c-0000-4000-8000-000000016642
                    compactBlockId: 860001
                    cropBlockId: 840017
                    tileId: TILE-840017
                    farmerId: 700002
                    landId: 810017
                    shg:
                      id: 52001
                      type: SHG
                      name: SHG A
                      code: SHG-52001
                    vo:
                      id: 4101
                      type: VO
                      name: VO A
                      code: VO-4101
                    acres: 3.25
                    joinedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                  totalAcres: 5.25
                  flags: []
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 100
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /compact-blocks/eligible-tiles:
    get:
      operationId: listEligibleTiles
      tags:
      - Compact blocks
      summary: Tiles eligible for a compact block
      description: |-
        **Use case.** Called online by the compact-block wizard after the user picks VOs and SHGs. The server applies the eligibility rules (current season, latest version per tile, geofence present, land with both survey and khata numbers, not in another active block, active farmer) and returns tiles grouped-ready by farmer with crop acres and the S2S flag. Offline the wizard uses the local equivalent.

        **Error codes.** `ROLE_NOT_ALLOWED`
      x-replaces: []
      x-features:
      - compact-block
      x-error-codes:
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: voIds
        in: query
        required: true
        style: form
        explode: false
        description: VO ids.
        schema:
          type: array
          items:
            type: integer
            format: int64
          minItems: 1
          maxItems: 200
        example:
        - 4101
      - name: shgIds
        in: query
        required: false
        style: form
        explode: false
        description: SHG ids; 0 = Non-SHG.
        schema:
          type: array
          items:
            type: integer
            format: int64
          maxItems: 500
        example:
        - 52001
      - name: excludeCompactBlockId
        in: query
        required: false
        description: 'When editing: treat this block''s own tiles as eligible.'
        schema:
          type: integer
          format: int64
        example: 860001
      responses:
        '200':
          description: Eligible tiles.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EligibleTilesResponse'
              example:
                season: 2025-26
                items:
                - cropBlockId: 840021
                  tileId: TILE-840021
                  farmer:
                    id: 700003
                    clientId: 3f6c1a2e-0000-4000-8000-000000061443
                    name: Farmer C
                  landId: 810021
                  surveyNo: SY-120
                  khataNo: KH-0021
                  acres: 2.75
                  shg:
                    id: 52001
                    type: SHG
                    name: SHG A
                    code: SHG-52001
                  vo:
                    id: 4101
                    type: VO
                    name: VO A
                    code: VO-4101
                  hasGeofence: true
                  isS2S: true
                  cropCodes:
                  - PADDY
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /assessments:
    get:
      operationId: listAssessments
      tags:
      - Surveys & assessments
      summary: Assessment definitions and past submissions
      description: '**Use case.** Called by data download (after farmers) and when the Certification survey, Activity Tracker
        or VO / SHG survey screens open, to get the assessment definitions (questions, options, rules) and the actor''s past
        submissions for the calendar / list. Submissions go through `/sync/batch` (`assessment_response`) with photos and
        signatures as `fileId`.'
      x-replaces:
      - GET /gateway/assessment-service/api/v1/assessmentInstanceAssesses
      x-features:
      - certification-survey
      - activity-tracker
      - vo-shg-surveys
      - data-download-cadre
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: type
        in: query
        required: false
        description: Assessment type.
        schema:
          $ref: '#/components/schemas/AssessmentType'
        example: ACTIVITY_TRACKER
      - name: assessee
        in: query
        required: false
        description: '`farmer:<id>`, `vo:<id>`, `shg:<id>` or `me`.'
        schema:
          type: string
          pattern: ^(farmer:\d+|vo:\d+|shg:\d+|me)$
        example: me
      - name: from
        in: query
        required: false
        description: Submissions on or after this date.
        schema:
          type: string
          format: date
        example: '2025-10-01'
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Assessments.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssessmentPage'
              example:
                assessments:
                - id: 910002
                  code: ASSESSMENT_CODE_ACTIVITY_TRACKER
                  type: ACTIVITY_TRACKER
                  title:
                    en: Activity tracker
                    te: Activity tracker (te)
                  assesseeType: USER
                  version: 3
                  questions:
                  - code: APCNF_SURVEY_ACTIVITYTRACKER_001
                    text:
                      en: Activities done today
                      te: Activities done today (te)
                    type: MULTI_CHOICE
                    required: true
                    minSelections: 1
                    options:
                    - code: FIELD_VISIT
                      label:
                        en: Field visit
                        te: Field visit (te)
                submissions:
                - id: 920001
                  clientId: c25fadb0-0000-4000-8000-000000000009
                  assessmentId: 910002
                  assesseeId: me
                  responseDate: '2025-10-13'
                  submittedAt: '2025-10-13T18:20:00+05:30'
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 100
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /analytics/dashboard:
    get:
      operationId: getAnalyticsDashboard
      tags:
      - Analytics
      summary: MIS dashboard for cadre or unit
      description: |-
        **Use case.** Called online by the Analytics dashboard (on open when the cache is older than today, on Refresh, and every `sync.analyticsRefreshHours` for Cadre). Returns yesterday / month / overall counts, nine-principles adoption, VO / SHG / cadre breakdown, drill-down farmer ids and the filter options in one response. The app caches it for the day and stores the filter selection locally.

        **Rules.**
        - `scope=unit` requires the Unit flow; `scope=cadre` returns only the caller's work area.

        **Error codes.** `ROLE_NOT_ALLOWED`
      x-replaces:
      - GET /rtld/api/dataset/v1/execute?name=PlusOneCadreAnalytics
      - GET /rtld/api/dataset/v1/execute?name=PlusOneAnalyticsIEIDCategoryList2
      - GET /rtld/api/dataset/v1/execute?name=PlusOneCadreAnalytics9Principles
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneCadreAnalyticsVOWiseSum/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneCadreAnalyticsSHGWiseSum/execute
      - GET /gateway/commons-report-service/api/v1/datasets/name/PlusOneCadreAnalytics9PrinciplesVOWise/execute
      - GET /rtld/api/dataset/v1/execute?name=PlusOneCadreAnalyticsVOWise
      x-features:
      - analytics-dashboard
      x-error-codes:
      - ROLE_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: scope
        in: query
        required: true
        description: '`cadre` or `unit`.'
        schema:
          type: string
          enum:
          - cadre
          - unit
        example: unit
      - name: cadreIds
        in: query
        required: false
        style: form
        explode: false
        description: Unit only.
        schema:
          type: array
          items:
            type: integer
            format: int64
        example:
        - 9001
      - name: voIds
        in: query
        required: false
        style: form
        explode: false
        description: VO filter.
        schema:
          type: array
          items:
            type: integer
            format: int64
        example:
        - 4101
      - name: shgIds
        in: query
        required: false
        style: form
        explode: false
        description: SHG filter (Unit).
        schema:
          type: array
          items:
            type: integer
            format: int64
        example:
        - 52001
      responses:
        '200':
          description: Dashboard.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyticsDashboard'
              example:
                scope: unit
                asOf: '2025-10-14T09:30:00+05:30'
                yesterday:
                  farmersSurveyed: 12
                  c1Farmers: 8
                  c1Acres: 14.5
                  s2sFarmers: 3
                  s2sAcres: 4.25
                month:
                  farmersSurveyed: 140
                  c1Farmers: 96
                  c1Acres: 180.5
                  s2sFarmers: 31
                  s2sAcres: 52.0
                  target: 300
                  coveragePercent: 46.7
                overall:
                  farmersSurveyed: 820
                  c1Farmers: 610
                  c1Acres: 1102.75
                  s2sFarmers: 205
                  s2sAcres: 330.5
                  target: 1000
                  coveragePercent: 82.0
                ninePrinciples:
                - code: P1_SOIL_COVER
                  label:
                    en: Soil always covered
                    te: Soil always covered (te)
                  farmers: 410
                  percent: 50.0
                breakdown:
                - level: VO
                  id: '4101'
                  name: VO A
                  counts:
                    farmersSurveyed: 220
                    c1Farmers: 170
                drilldowns:
                - metric: c1Farmers
                  period: YESTERDAY
                  farmerIds:
                  - 700001
                  - 700002
                filterOptions:
                  cadres:
                  - userId: 9001
                    displayName: User A
                  vos:
                  - id: 4101
                    type: VO
                    name: VO A
                    code: VO-4101
                  shgs:
                  - id: 52001
                    type: SHG
                    name: SHG A
                    code: SHG-52001
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /notifications:
    get:
      operationId: listNotifications
      tags:
      - Notifications
      summary: MIS and system notifications since a time
      description: |-
        **Use case.** Called when the Notifications screen opens or is pulled to refresh, and when a data-only push arrives (pushes carry ids only, never names). Returns endorsement status / comment notifications and broadcasts for the user; the app stores them in its `notification` table and opens the farmer hub on tap.

        **Rules.**
        - Only notifications about farmers in the user's scope, posted by someone else.
      x-replaces:
      - GET /gateway/asset-service/api/v1/endorsementComment/postedby?page&size&postedBy
      x-features:
      - notifications
      - home-navigation
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - name: since
        in: query
        required: false
        description: Return notifications created after this time.
        schema:
          type: string
          format: date-time
        example: '2025-10-14T09:30:00+05:30'
      - name: category
        in: query
        required: false
        description: MIS or SYS.
        schema:
          type: string
          enum:
          - MIS
          - SYS
        example: MIS
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Notifications.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationPage'
              example:
                items:
                - id: 930001
                  category: MIS
                  kind: ENDORSEMENT_COMMENT
                  title:
                    en: New comment
                    te: New comment (te)
                  body:
                    en: A reviewer commented on a farmer.
                    te: A reviewer commented on a farmer. (te)
                  farmerId: 700001
                  entityType: FARMER
                  entityId: 700001
                  createdAt: '2025-10-14T09:30:00+05:30'
                page:
                  nextCursor: null
                  hasMore: false
                  pageSize: 50
                serverTime: '2025-10-14T09:31:02+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /sync/batch:
    post:
      operationId: submitSyncBatch
      tags:
      - Sync
      summary: Apply an ordered batch of offline mutations
      description: |-
        **Use case.** Called by the single outbox `CoroutineWorker` whenever ops are ready and the network is up (periodic, after every save, and expedited for "Sync now" / Force sync / per-item retry on Pending Sync). It takes up to 100 ready ops in `createdAt` order (after their files are uploaded) and sends them as one batch. Every user write in the app goes through here: farmer create / edit / category / photo / roles, land (incl. leased-in, delete, geofence), livestock, crop stock, Crop Actual blocks (PMDS / A Grade), crop and ATM harvests, ATM and DKG, KAP submissions, compact blocks and member removal, endorsement transitions, comments and abstract uploads, and assessment responses (certification, activity tracker, VO / SHG surveys). The app stores `serverId` and `serverVersion` per result, marks `applied`/`duplicate` done, applies the conflict policy for `conflict`, marks `rejected` as failed with the localised reason (visible and retryable on Pending Sync and the sync error log), keeps `blocked` pending and backs off on `retry`.

        **Rules.**
        - Ops are applied in order; client UUID references (`refs`, `*ClientId` fields) resolve through a per-tenant client-to-server id map, so children may reference parents created earlier in the same batch.
        - Each op is idempotent on `opId` (kept >= 30 days): a resend returns `duplicate` with the original result.
        - `update` payloads are JSON Merge Patches (RFC 7396) of changed fields; `required` constraints of the entity schema apply to the merged result. Stale `baseVersion` -> `conflict` with `serverState`, except append-only entities (harvests, comments, assessment responses) which never conflict.
        - Conflict policy: farmer = field-level merge, server wins on locked fields (frozen category, admin life status); land = rejected if authenticated or disabled since baseVersion, acreage changes re-run the land-usage check; crop_block = version check, `PART_OF_COMPACT_BLOCK` when in a block; farming_model ATM = version check, DKG = last write wins per month; compact_block = server-authoritative membership; endorsement_action = state machine; kap_plan = rejected once endorsed.
        - Validation rules (same numbers as `ConfigBundle.formLimits`): land usage `LAND_AREA_EXCEEDED` / `LAND_ACRES_BELOW_USAGE`; category gates `CATEGORY_FROZEN` / `CATEGORY_NOT_ALLOWED`; PMDS ranges; A Grade crop counts; compact block >= 5 acres; ATM C5 <= 0.5 acre; KAP ceilings; geofence polygon >= 3 points.
        - A failed op makes later ops that depend on it (`dependsOn` or a reference) `blocked`; independent ops still apply.
        - Transient failures of a single op return `retry`; the HTTP status stays 200.
        - Large batches may return 202 with a status URL (`GET /sync/batches/{batchId}`).
        - Every mutation is audited (user, device, app version, time); push fan-out (e.g. endorsement status to the cadre) happens after commit.

        **Error codes.** `BATCH_TOO_LARGE`, `IDEMPOTENCY_KEY_REUSED`, `STALE_VERSION`, `UNKNOWN_REFERENCE`, `DEPENDENCY_FAILED`, `LOCKED_FIELD`, `DUPLICATE_FARMER`, `INVALID_AADHAAR`, `INVALID_MOBILE`, `AGE_OUT_OF_RANGE`, `SHG_REQUIRES_FEMALE`, `CATEGORY_FROZEN`, `CATEGORY_REQUIRED`, `CATEGORY_NOT_ALLOWED`, `FARMER_INACTIVE`, `FARMER_EDIT_DISABLED`, `CONSENT_REQUIRED`, `REASON_REQUIRED`, `LAND_AREA_EXCEEDED`, `LAND_ACRES_BELOW_USAGE`, `LAND_AUTHENTICATED_LOCKED`, `LAND_DISABLED`, `LAND_IN_USE`, `LAND_CHECKSUM_MISMATCH`, `LAND_NOT_AUTHENTICATED`, `LEASE_LIMIT_EXCEEDED`, `LESSOR_IS_SELF`, `KHATA_REQUIRED`, `SURVEY_REQUIRED`, `POLYGON_TOO_FEW_POINTS`, `POLYGON_NOT_CLOSED`, `POLYGON_SELF_INTERSECTING`, `GEOFENCE_ACCURACY_TOO_LOW`, `GEOFENCE_ZERO_ACRE_LAND`, `INVALID_KML`, `CROP_TYPE_DISABLED`, `A_GRADE_CROP_COUNT`, `A_GRADE_ASSOCIATED_CROPS_MIN`, `PMDS_SEED_VARIETIES_RANGE`, `PMDS_SEED_QUANTITY_RANGE`, `PRACTICE_REQUIRED`, `PART_OF_COMPACT_BLOCK`, `CROP_HARVESTED_LOCKED`, `SOWING_DATE_OUT_OF_RANGE`, `EDIT_REASON_REQUIRED`, `FMT_EDIT_DISABLED`, `DUPLICATE_TILE`, `HARVEST_SURPLUS_EXCEEDS_TOTAL`, `HARVEST_DATE_INVALID`, `HARVEST_TYPE_DISABLED`, `HARVEST_PHOTO_REQUIRED`, `HARVEST_ALREADY_COMPLETED`, `ATM_C5_AREA_EXCEEDED`, `ATM_C5_LIMIT_ONE`, `ATM_LAND_REQUIRED`, `KAP_DISABLED`, `KAP_LOCKED_ENDORSED`, `KAP_VALUE_EXCEEDS_LIMIT`, `KAP_PMDS_YEARS_REQUIRED`, `KAP_DGC_SEASON_REQUIRED`, `ENDORSEMENT_ORDER`, `ENDORSEMENT_TRANSITION_INVALID`, `ENDORSEMENT_KAP_NOT_SUBMITTED`, `ENDORSEMENT_CATEGORY_NOT_FROZEN`, `ENDORSEMENT_FARMER_NOT_REGISTERED`, `COMMENT_EMPTY`, `COMPACT_BLOCK_BELOW_5_ACRES`, `COMPACT_BLOCK_NAME_TAKEN`, `TILE_NOT_ELIGIBLE`, `TILE_IN_OTHER_BLOCK`, `TILE_NO_GEOFENCE`, `ASSESSMENT_INCOMPLETE`, `SIGNATURE_REQUIRED`, `ACTIVITY_SUBMIT_TOO_EARLY`, `ACTIVITY_ALREADY_SUBMITTED`, `ACTIVITY_EDIT_WINDOW_CLOSED`, `FUTURE_DATE`, `FILE_NOT_FOUND`, `FILE_NOT_COMPLETE`, `FEATURE_DISABLED`
      x-replaces:
      - POST /gateway/asset-service/api/v1/endorsement/bulk/save-all
      - POST /ptld/api/customworknode/v1/worknodeietag/urvi
      - POST /ptld/api/customworknode/v1/worknodeietag/urvi/ie
      - PATCH /ptld/api/customworknode/v1/worknodeietag/urvi/ie
      - PUT /ctld/api/ie/v1/iconpic
      - POST /ctld/api/ierolemap/v1
      - DELETE /ctld/api/ierolemap/v1?iERoleMapId=
      - POST /ctld/api/inventory/inventoryproduct/custominventoryproduct/v1
      - PATCH /ctld/api/inventory/inventoryproduct/v1?inventoryId=
      - POST /gateway/asset-service/api/v1/assets/
      - PATCH /gateway/asset-service/api/v1/assets/
      - DELETE /gateway/asset-service/api/v1/assets/{id}?reason=
      - POST /gateway/asset-service/api/v1/asset/attachment
      - POST /gateway/portfolio-service/api/v1/portfolios/line-items/v2?logCropActivity=true&actorType&actorId
      - PATCH /gateway/portfolio-service/api/v1/portfolios/
      - PUT /gateway/portfolio-service/api/v1/portfolios/{pid}/portfolioLineItem/v2/crop-activity
      - DELETE /gateway/portfolio-service/api/v1/portfolios/portfolioLineItem/?logCropActivity&portfolioId&portfolioLineItemId&portfolioLineItemUUID
      - POST /gateway/portfolio-service/api/v1/portfolios/line-items/attachment/item/custom
      - POST /gateway/portfolio-service/api/v1/farming-model
      - POST /gateway/portfolio-service/api/v1/farming-model/attachment/custom
      - POST /gateway/portfolio-service/api/v1/farming-model/harvest/{entityId}/{entityType}
      - POST /gateway/portfolio-service/api/v1/portfolios/harvest/{id}/ENTITY_TYPE.IE
      - POST /gateway/portfolio-service/api/v1/compact-block
      - PUT /gateway/portfolio-service/api/v1/compact-block
      - DELETE /gateway/portfolio-service/api/v1/compact-block?compactBlockId&reason
      - DELETE /gateway/portfolio-service/api/v1/compact-block/{cbId}/compact-block-member?reason&compactBlockMemberId
      - POST /gateway/asset-service/api/v1/endorsements/attachment?endorsementID&endorsementUUID
      - POST /gateway/asset-service/api/v1/endorsementComment
      - POST /gateway/asset-service/api/v1/annual-action-plan-extended
      - PUT /gateway/asset-service/api/v1/annual-action-plan-extended
      - PATCH /gateway/asset-service/api/v1/annual-action-plans
      - POST /gateway/assessment-service/api/v1/aiadefaultresponses/attachment/uuid
      x-features:
      - add-edit-farmer-ie-profile
      - farmer-member-category
      - land-assets
      - leased-land
      - geofencing
      - livestock
      - crop-stock-inventory
      - dkg-kitchen-garden
      - atm-model
      - atm-harvest
      - crop-actual-harvest
      - crop-actual
      - pmds-a-grade-crop-details
      - certification-survey
      - compact-block
      - activity-tracker
      - kap-25-26
      - farmer-endorsement-details
      - vo-shg-farmer-endorsement
      - vo-shg-surveys
      - pending-sync
      - settings
      - sync-error-log
      x-error-codes:
      - BATCH_TOO_LARGE
      - IDEMPOTENCY_KEY_REUSED
      - STALE_VERSION
      - UNKNOWN_REFERENCE
      - DEPENDENCY_FAILED
      - LOCKED_FIELD
      - DUPLICATE_FARMER
      - INVALID_AADHAAR
      - INVALID_MOBILE
      - AGE_OUT_OF_RANGE
      - SHG_REQUIRES_FEMALE
      - CATEGORY_FROZEN
      - CATEGORY_REQUIRED
      - CATEGORY_NOT_ALLOWED
      - FARMER_INACTIVE
      - FARMER_EDIT_DISABLED
      - CONSENT_REQUIRED
      - REASON_REQUIRED
      - LAND_AREA_EXCEEDED
      - LAND_ACRES_BELOW_USAGE
      - LAND_AUTHENTICATED_LOCKED
      - LAND_DISABLED
      - LAND_IN_USE
      - LAND_CHECKSUM_MISMATCH
      - LAND_NOT_AUTHENTICATED
      - LEASE_LIMIT_EXCEEDED
      - LESSOR_IS_SELF
      - KHATA_REQUIRED
      - SURVEY_REQUIRED
      - POLYGON_TOO_FEW_POINTS
      - POLYGON_NOT_CLOSED
      - POLYGON_SELF_INTERSECTING
      - GEOFENCE_ACCURACY_TOO_LOW
      - GEOFENCE_ZERO_ACRE_LAND
      - INVALID_KML
      - CROP_TYPE_DISABLED
      - A_GRADE_CROP_COUNT
      - A_GRADE_ASSOCIATED_CROPS_MIN
      - PMDS_SEED_VARIETIES_RANGE
      - PMDS_SEED_QUANTITY_RANGE
      - PRACTICE_REQUIRED
      - PART_OF_COMPACT_BLOCK
      - CROP_HARVESTED_LOCKED
      - SOWING_DATE_OUT_OF_RANGE
      - EDIT_REASON_REQUIRED
      - FMT_EDIT_DISABLED
      - DUPLICATE_TILE
      - HARVEST_SURPLUS_EXCEEDS_TOTAL
      - HARVEST_DATE_INVALID
      - HARVEST_TYPE_DISABLED
      - HARVEST_PHOTO_REQUIRED
      - HARVEST_ALREADY_COMPLETED
      - ATM_C5_AREA_EXCEEDED
      - ATM_C5_LIMIT_ONE
      - ATM_LAND_REQUIRED
      - KAP_DISABLED
      - KAP_LOCKED_ENDORSED
      - KAP_VALUE_EXCEEDS_LIMIT
      - KAP_PMDS_YEARS_REQUIRED
      - KAP_DGC_SEASON_REQUIRED
      - ENDORSEMENT_ORDER
      - ENDORSEMENT_TRANSITION_INVALID
      - ENDORSEMENT_KAP_NOT_SUBMITTED
      - ENDORSEMENT_CATEGORY_NOT_FROZEN
      - ENDORSEMENT_FARMER_NOT_REGISTERED
      - COMMENT_EMPTY
      - COMPACT_BLOCK_BELOW_5_ACRES
      - COMPACT_BLOCK_NAME_TAKEN
      - TILE_NOT_ELIGIBLE
      - TILE_IN_OTHER_BLOCK
      - TILE_NO_GEOFENCE
      - ASSESSMENT_INCOMPLETE
      - SIGNATURE_REQUIRED
      - ACTIVITY_SUBMIT_TOO_EARLY
      - ACTIVITY_ALREADY_SUBMITTED
      - ACTIVITY_EDIT_WINDOW_CLOSED
      - FUTURE_DATE
      - FILE_NOT_FOUND
      - FILE_NOT_COMPLETE
      - FEATURE_DISABLED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncBatchRequest'
            example:
              batchId: 5d4c3b2a-0000-4000-8000-000000000193
              clientTime: '2025-10-14T09:30:00+05:30'
              ops:
              - opId: e0a1b2c3-0000-4000-8000-000000000001
                entityType: farmer
                op: create
                clientId: 3f6c1a2e-0000-4000-8000-000000061442
                baseVersion: null
                clientTime: '2025-10-14T09:30:00+05:30'
                payload:
                  clientId: 3f6c1a2e-0000-4000-8000-000000061442
                  name: Farmer B
                  gender: FEMALE
                  age: 35
                  mobile: '9000000002'
                  identity:
                    aadhaar: '999999992345'
                  maritalStatus: SINGLE
                  family:
                    relation: FATHER
                    name: Father B
                  education: PRIMARY
                  socialCategory: SC
                  village:
                    id: 3101
                    type: VILLAGE
                    name: Village A
                    code: VIL-3101
                  habitation:
                    id: 3901
                    type: HABITATION
                    name: Habitation A
                    code: HAB-3901
                  vo:
                    id: 4101
                    type: VO
                    name: VO A
                    code: VO-4101
                  shg:
                    id: 52001
                    type: SHG
                    name: SHG A
                    code: SHG-52001
                  roles:
                  - roleCode: MEMBER
                  category:
                    code: FARMER
                  status:
                    lifeStatus: ACTIVE
                  photo:
                    fileId: 7a6b5c4d-0000-4000-8000-000000000226
                    kind: PHOTO_FARMER
                  consentGiven: true
              - opId: e0a1b2c3-0000-4000-8000-000000000002
                entityType: land
                op: create
                clientId: 4a7d2b3f-0000-4000-8000-000000004098
                baseVersion: null
                refs:
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061442
                dependsOn:
                - e0a1b2c3-0000-4000-8000-000000000001
                payload:
                  clientId: 4a7d2b3f-0000-4000-8000-000000004098
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061442
                  category: LEASED_IN
                  landType: RAINFED
                  acres: 0.75
                  authenticated: false
                  lease:
                    leaseType: INCOMING
                    lessorFarmerId: 700001
                    lessorLandId: 810001
                    lessorWorkNode:
                      id: 52001
                      type: SHG
                      name: SHG A
                      code: SHG-52001
                    leasedAcres: 0.75
              - opId: e0a1b2c3-0000-4000-8000-000000000003
                entityType: crop_block
                op: update
                clientId: 5b8e3c4a-0000-4000-8000-000000008193
                serverId: 840001
                baseVersion: 4
                refs:
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                payload:
                  clientId: 5b8e3c4a-0000-4000-8000-000000008193
                  practiceCodes:
                  - AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING
              - opId: e0a1b2c3-0000-4000-8000-000000000004
                entityType: crop_harvest
                op: create
                clientId: 9f2c7a8e-0000-4000-8000-000000000001
                refs:
                  cropBlockClientId: 5b8e3c4a-0000-4000-8000-000000008193
                payload:
                  clientId: 9f2c7a8e-0000-4000-8000-000000000001
                  kind: CROP
                  cropBlockId: 840001
                  cropBlockClientId: 5b8e3c4a-0000-4000-8000-000000008193
                  cropLineClientId: 5b8e3c4a-0000-4000-8000-000000008449
                  productCode: PADDY
                  harvestType: WHOLE_CROP
                  totalOutputQuintals: 18.5
                  surplusQuintals: 10
                  harvestDate: '2025-10-12'
                  interestedInMarketing: true
                  photo:
                    fileId: 7a6b5c4d-0000-4000-8000-000000000226
                    kind: PHOTO_HARVEST
                  location:
                    lat: 16.5005
                    lng: 80.1006
                    accuracyM: 6
              - opId: e0a1b2c3-0000-4000-8000-000000000005
                entityType: kap_plan
                op: create
                clientId: a03d8b9f-0000-4000-8000-000000000001
                refs:
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                payload:
                  clientId: a03d8b9f-0000-4000-8000-000000000001
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  season: 2025-26
                  previousCurrent:
                    c1Acres: 2.0
                    pmdsAcres: 0.5
                    s2sAcres: 1.0
                    aGradeAcres: 0
                    s2sInCompactBlockAcres: 0.5
                    desiCows: 2
                    dkg: SEASONAL
                    atm: true
                  current:
                    c1Acres: 2.5
                    pmdsAcres: 1.0
                    s2sAcres: 1.5
                    aGradeAcres: 0.5
                    s2sInCompactBlockAcres: 1.0
                    desiCows: 2
                    dkg: DKG_365
                    atm: true
                  pmdsPractisingYears:
                  - 2022-23
                  - 2023-24
                  dgc:
                    practising: true
                    seasons:
                    - KHARIF
                    - RABI
              - opId: e0a1b2c3-0000-4000-8000-000000000006
                entityType: compact_block_member
                op: delete
                clientId: 7d0a5e6c-0000-4000-8000-000000016642
                serverId: 861002
                baseVersion: 1
                refs:
                  compactBlockClientId: 7d0a5e6c-0000-4000-8000-000000016385
                reason: Farmer removed from block
              - opId: e0a1b2c3-0000-4000-8000-000000000007
                entityType: endorsement_action
                op: create
                clientId: b14e9caf-0000-4000-8000-000000000001
                payload:
                  clientId: b14e9caf-0000-4000-8000-000000000001
                  entityType: FARMER
                  entityId: 700001
                  action: ENDORSE
                  comment: Verified in field visit
              - opId: e0a1b2c3-0000-4000-8000-000000000008
                entityType: farming_model
                op: create
                clientId: 6c9f4d5b-0000-4000-8000-000000012291
                refs:
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                payload:
                  clientId: 6c9f4d5b-0000-4000-8000-000000012291
                  modelType: DKG_365
                  farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                  month: 2025-10
                  continuing: true
        description: Ordered mutations. The `Idempotency-Key` header is the batch-level key (use `batchId`); each op carries
          its own `opId`.
      responses:
        '200':
          description: Per-op results (the batch itself succeeded even if some ops were rejected).
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncBatchResult'
              example:
                batchId: 5d4c3b2a-0000-4000-8000-000000000193
                processedAt: '2025-10-14T09:31:02+05:30'
                changesCursor: chg_eyJ2IjoxMjQwMH0
                results:
                - opId: e0a1b2c3-0000-4000-8000-000000000001
                  clientId: 3f6c1a2e-0000-4000-8000-000000061442
                  entityType: farmer
                  status: applied
                  serverId: 700002
                  serverVersion: 1
                  error: null
                  serverState: null
                - opId: e0a1b2c3-0000-4000-8000-000000000002
                  clientId: 4a7d2b3f-0000-4000-8000-000000004098
                  entityType: land
                  status: applied
                  serverId: 810002
                  serverVersion: 1
                  error: null
                  serverState: null
                - opId: e0a1b2c3-0000-4000-8000-000000000003
                  clientId: 5b8e3c4a-0000-4000-8000-000000008193
                  entityType: crop_block
                  status: rejected
                  serverId: 840001
                  serverVersion: 5
                  error:
                    type: https://urvi.example.invalid/problems/part-of-compact-block
                    title: Part of a compact block
                    status: 422
                    detail: This crop block is part of compact block CB 1 and cannot be edited.
                    code: PART_OF_COMPACT_BLOCK
                    requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                    instance: /sync/batch#ops/2
                  serverState:
                    id: 840001
                    clientId: 5b8e3c4a-0000-4000-8000-000000008193
                    version: 5
                    createdAt: '2025-10-01T08:00:00+05:30'
                    updatedAt: '2025-10-14T09:30:00+05:30'
                    active: true
                    farmerId: 700001
                    farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                    landId: 810001
                    landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                    landChecksum: c1a5e0d7
                    tileId: TILE-840001
                    planType: SEASON_ACTUAL
                    season: 2025-26
                    month: 2025-10
                    cropType: AGRICULTURE
                    crops:
                    - clientId: 5b8e3c4a-0000-4000-8000-000000008449
                      id: 841001
                      productCode: PADDY
                      acres: 1.5
                      isMajor: true
                      seedVariety: BPT-5204
                      expectedHarvestDate: '2025-12-15'
                      harvestStatus: NOT_STARTED
                      placeholder: false
                    - clientId: 5b8e3c4a-0000-4000-8000-000000008450
                      id: 841002
                      productCode: RED_GRAM
                      acres: 0.5
                      isMajor: false
                      harvestStatus: NOT_STARTED
                      placeholder: false
                    practiceCodes:
                    - AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING
                    - AP-GOV-KAP-BEEJAMRUTHAM
                    sowingDate: '2025-07-10'
                    photo:
                      fileId: 7a6b5c4d-0000-4000-8000-000000000225
                      kind: PHOTO_CROP
                      url: https://files.example.invalid/urvi/signed/crop-840001.jpg
                    geofence:
                      polygon:
                        type: Polygon
                        coordinates:
                        - - - 80.1001
                            - 16.5001
                          - - 80.1012
                            - 16.5001
                          - - 80.1012
                            - 16.501
                          - - 80.1001
                            - 16.501
                          - - 80.1001
                            - 16.5001
                      captureMethod: OSM_MOTION
                      accuracyM: 4.5
                      pointCount: 4
                      capturedAt: '2025-10-14T09:30:00+05:30'
                      areaAcres: 2.4
                      kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                    changeLog: []
                    compactBlockId: 860001
                    editable: false
                    totalAcres: 2.0
                - opId: e0a1b2c3-0000-4000-8000-000000000004
                  clientId: 9f2c7a8e-0000-4000-8000-000000000001
                  entityType: crop_harvest
                  status: duplicate
                  serverId: 940001
                  serverVersion: 1
                  error: null
                  serverState: null
                - opId: e0a1b2c3-0000-4000-8000-000000000005
                  clientId: a03d8b9f-0000-4000-8000-000000000001
                  entityType: kap_plan
                  status: applied
                  serverId: 870001
                  serverVersion: 3
                  error: null
                  serverState: null
                - opId: e0a1b2c3-0000-4000-8000-000000000006
                  clientId: 7d0a5e6c-0000-4000-8000-000000016642
                  entityType: compact_block_member
                  status: applied
                  serverId: 861002
                  serverVersion: 2
                  error: null
                  serverState: null
                - opId: e0a1b2c3-0000-4000-8000-000000000007
                  clientId: b14e9caf-0000-4000-8000-000000000001
                  entityType: endorsement_action
                  status: rejected
                  serverId: null
                  serverVersion: null
                  error:
                    type: https://urvi.example.invalid/problems/endorsement-kap-not-submitted
                    title: KAP not submitted
                    status: 422
                    detail: Submit KAP before endorsing this farmer.
                    code: ENDORSEMENT_KAP_NOT_SUBMITTED
                    requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                    instance: /sync/batch#ops/6
                  serverState: null
                - opId: e0a1b2c3-0000-4000-8000-000000000008
                  clientId: 6c9f4d5b-0000-4000-8000-000000012291
                  entityType: farming_model
                  status: applied
                  serverId: 850003
                  serverVersion: 1
                  error: null
                  serverState: null
                idMap:
                - entityType: farmer
                  clientId: 3f6c1a2e-0000-4000-8000-000000061442
                  serverId: 700002
                - entityType: land
                  clientId: 4a7d2b3f-0000-4000-8000-000000004098
                  serverId: 810002
                - entityType: kap_plan
                  clientId: a03d8b9f-0000-4000-8000-000000000001
                  serverId: 870001
                - entityType: farming_model
                  clientId: 6c9f4d5b-0000-4000-8000-000000012291
                  serverId: 850003
        '202':
          description: Accepted for asynchronous processing; poll `Location`.
          headers:
            Location:
              $ref: '#/components/headers/Location'
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncBatchStatus'
              example:
                batchId: 5d4c3b2a-0000-4000-8000-000000000193
                state: QUEUED
                receivedAt: '2025-10-14T09:30:00+05:30'
                progress:
                  total: 100
                  processed: 0
                result: null
        '400':
          description: Malformed batch (schema) or too many ops.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              examples:
                BATCH_TOO_LARGE:
                  summary: Too many operations
                  value:
                    type: https://urvi.example.invalid/problems/batch-too-large
                    title: Too many operations
                    status: 400
                    detail: A batch may contain at most 100 operations.
                    code: BATCH_TOO_LARGE
                    requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                VALIDATION_FAILED:
                  summary: Validation failed
                  value:
                    type: https://urvi.example.invalid/problems/validation-failed
                    title: Validation failed
                    status: 400
                    detail: ops[3].payload.totalOutputQuintals must be <= 999.99
                    code: VALIDATION_FAILED
                    requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                    errors:
                    - field: /ops/3/payload/totalOutputQuintals
                      code: VALIDATION_FAILED
                      message: Must be at most 999.99 quintals.
                      limit: 999.99
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: The same `batchId` is still being processed.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/idempotency-key-reused
                title: Batch in progress
                status: 409
                detail: This batch is already being processed; poll its status.
                code: IDEMPOTENCY_KEY_REUSED
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '412':
          description: Precondition failed for the whole batch (e.g. device binding lost).
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/precondition-failed
                title: Device not bound
                status: 412
                detail: This device is no longer bound to the user.
                code: PRECONDITION_FAILED
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '422':
          description: The `Idempotency-Key` was reused with a different body. Per-op rule violations are NOT returned here;
            they appear as `rejected` results with HTTP 200.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/idempotency-key-reused
                title: Idempotency key reused
                status: 422
                detail: This key was used for a different request body.
                code: IDEMPOTENCY_KEY_REUSED
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /sync/batches/{batchId}:
    get:
      operationId: getSyncBatch
      tags:
      - Sync
      summary: Status of an asynchronously processed batch
      description: |-
        **Use case.** Called by the sync worker after a 202 from `/sync/batch`, honouring `Retry-After`, until `state` is COMPLETED or FAILED; then the app handles `result` exactly like a 200 batch response. Also used by Pending Sync to show progress of a long batch.

        **Rules.**
        - Batches are visible only to the user and device that sent them; results are kept 30 days.

        **Error codes.** `NOT_FOUND`
      x-replaces: []
      x-features:
      - pending-sync
      - settings
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/BatchId'
      responses:
        '200':
          description: Batch status.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncBatchStatus'
              example:
                batchId: 5d4c3b2a-0000-4000-8000-000000000193
                state: COMPLETED
                receivedAt: '2025-10-14T09:30:00+05:30'
                completedAt: '2025-10-14T09:31:02+05:30'
                progress:
                  total: 8
                  processed: 8
                result:
                  batchId: 5d4c3b2a-0000-4000-8000-000000000193
                  processedAt: '2025-10-14T09:31:02+05:30'
                  changesCursor: chg_eyJ2IjoxMjQwMH0
                  results:
                  - opId: e0a1b2c3-0000-4000-8000-000000000001
                    clientId: 3f6c1a2e-0000-4000-8000-000000061442
                    entityType: farmer
                    status: applied
                    serverId: 700002
                    serverVersion: 1
                    error: null
                    serverState: null
                  - opId: e0a1b2c3-0000-4000-8000-000000000002
                    clientId: 4a7d2b3f-0000-4000-8000-000000004098
                    entityType: land
                    status: applied
                    serverId: 810002
                    serverVersion: 1
                    error: null
                    serverState: null
                  - opId: e0a1b2c3-0000-4000-8000-000000000003
                    clientId: 5b8e3c4a-0000-4000-8000-000000008193
                    entityType: crop_block
                    status: rejected
                    serverId: 840001
                    serverVersion: 5
                    error:
                      type: https://urvi.example.invalid/problems/part-of-compact-block
                      title: Part of a compact block
                      status: 422
                      detail: This crop block is part of compact block CB 1 and cannot be edited.
                      code: PART_OF_COMPACT_BLOCK
                      requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                      instance: /sync/batch#ops/2
                    serverState:
                      id: 840001
                      clientId: 5b8e3c4a-0000-4000-8000-000000008193
                      version: 5
                      createdAt: '2025-10-01T08:00:00+05:30'
                      updatedAt: '2025-10-14T09:30:00+05:30'
                      active: true
                      farmerId: 700001
                      farmerClientId: 3f6c1a2e-0000-4000-8000-000000061441
                      landId: 810001
                      landClientId: 4a7d2b3f-0000-4000-8000-000000004097
                      landChecksum: c1a5e0d7
                      tileId: TILE-840001
                      planType: SEASON_ACTUAL
                      season: 2025-26
                      month: 2025-10
                      cropType: AGRICULTURE
                      crops:
                      - clientId: 5b8e3c4a-0000-4000-8000-000000008449
                        id: 841001
                        productCode: PADDY
                        acres: 1.5
                        isMajor: true
                        seedVariety: BPT-5204
                        expectedHarvestDate: '2025-12-15'
                        harvestStatus: NOT_STARTED
                        placeholder: false
                      - clientId: 5b8e3c4a-0000-4000-8000-000000008450
                        id: 841002
                        productCode: RED_GRAM
                        acres: 0.5
                        isMajor: false
                        harvestStatus: NOT_STARTED
                        placeholder: false
                      practiceCodes:
                      - AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING
                      - AP-GOV-KAP-BEEJAMRUTHAM
                      sowingDate: '2025-07-10'
                      photo:
                        fileId: 7a6b5c4d-0000-4000-8000-000000000225
                        kind: PHOTO_CROP
                        url: https://files.example.invalid/urvi/signed/crop-840001.jpg
                      geofence:
                        polygon:
                          type: Polygon
                          coordinates:
                          - - - 80.1001
                              - 16.5001
                            - - 80.1012
                              - 16.5001
                            - - 80.1012
                              - 16.501
                            - - 80.1001
                              - 16.501
                            - - 80.1001
                              - 16.5001
                        captureMethod: OSM_MOTION
                        accuracyM: 4.5
                        pointCount: 4
                        capturedAt: '2025-10-14T09:30:00+05:30'
                        areaAcres: 2.4
                        kmlUrl: https://files.example.invalid/urvi/signed/land-1001.kml
                      changeLog: []
                      compactBlockId: 860001
                      editable: false
                      totalAcres: 2.0
                  - opId: e0a1b2c3-0000-4000-8000-000000000004
                    clientId: 9f2c7a8e-0000-4000-8000-000000000001
                    entityType: crop_harvest
                    status: duplicate
                    serverId: 940001
                    serverVersion: 1
                    error: null
                    serverState: null
                  - opId: e0a1b2c3-0000-4000-8000-000000000005
                    clientId: a03d8b9f-0000-4000-8000-000000000001
                    entityType: kap_plan
                    status: applied
                    serverId: 870001
                    serverVersion: 3
                    error: null
                    serverState: null
                  - opId: e0a1b2c3-0000-4000-8000-000000000006
                    clientId: 7d0a5e6c-0000-4000-8000-000000016642
                    entityType: compact_block_member
                    status: applied
                    serverId: 861002
                    serverVersion: 2
                    error: null
                    serverState: null
                  - opId: e0a1b2c3-0000-4000-8000-000000000007
                    clientId: b14e9caf-0000-4000-8000-000000000001
                    entityType: endorsement_action
                    status: rejected
                    serverId: null
                    serverVersion: null
                    error:
                      type: https://urvi.example.invalid/problems/endorsement-kap-not-submitted
                      title: KAP not submitted
                      status: 422
                      detail: Submit KAP before endorsing this farmer.
                      code: ENDORSEMENT_KAP_NOT_SUBMITTED
                      requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                      instance: /sync/batch#ops/6
                    serverState: null
                  - opId: e0a1b2c3-0000-4000-8000-000000000008
                    clientId: 6c9f4d5b-0000-4000-8000-000000012291
                    entityType: farming_model
                    status: applied
                    serverId: 850003
                    serverVersion: 1
                    error: null
                    serverState: null
                  idMap:
                  - entityType: farmer
                    clientId: 3f6c1a2e-0000-4000-8000-000000061442
                    serverId: 700002
                  - entityType: land
                    clientId: 4a7d2b3f-0000-4000-8000-000000004098
                    serverId: 810002
                  - entityType: kap_plan
                    clientId: a03d8b9f-0000-4000-8000-000000000001
                    serverId: 870001
                  - entityType: farming_model
                    clientId: 6c9f4d5b-0000-4000-8000-000000012291
                    serverId: 850003
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /files:
    post:
      operationId: createFileUpload
      tags:
      - Files
      summary: Create a file upload (resumable or single-shot)
      description: |-
        **Use case.** Called by the outbox worker before the ops that reference a file: farmer photo, crop / ATM / harvest / activity photos, land / crop / ATM KML geofences, certification signatures, assessment attachments, endorsement abstracts and remote-action diagnostics. The file may be uploaded before its parent exists on the server; ops reference it by `fileId`. JSON body creates a resumable upload (then `PATCH /files/{fileId}` with `Upload-Offset`); `multipart/form-data` uploads a small file (<= 1 MB) in one request and returns COMPLETE. Never re-uploads server images and never falls back to another store.

        **Rules.**
        - Idempotent on `clientId`: a repeat returns the existing upload with its current offset.
        - Allowed types per kind: images (jpeg, png, webp) for PHOTO_*; `application/vnd.google-earth.kml+xml` for KML_GEOFENCE; png for SIGNATURE; image, PDF or Word for ENDORSEMENT_ABSTRACT; encrypted zip for DIAGNOSTICS.
        - Max 20 MB; incomplete uploads expire after 7 days.

        **Error codes.** `FILE_TOO_LARGE`, `UNSUPPORTED_MEDIA_TYPE`, `FILE_CHECKSUM_MISMATCH`, `INVALID_KML`
      x-replaces:
      - PUT /ctld/api/ie/v1/iconpic
      - POST /gateway/asset-service/api/v1/asset/attachment
      - POST /gateway/portfolio-service/api/v1/portfolios/line-items/attachment/item/custom
      - POST /gateway/portfolio-service/api/v1/farming-model/attachment/custom
      - POST /gateway/asset-service/api/v1/endorsements/attachment?endorsementID&endorsementUUID
      - POST /gateway/assessment-service/api/v1/aiadefaultresponses/attachment/uuid
      - SDK Firebase Storage userId/farmerId/pliId/pliId.ext
      x-features:
      - add-edit-farmer-ie-profile
      - land-assets
      - leased-land
      - geofencing
      - crop-actual
      - atm-model
      - atm-harvest
      - crop-actual-harvest
      - certification-survey
      - activity-tracker
      - vo-shg-farmer-endorsement
      - vo-shg-surveys
      - device-registration
      - pending-sync
      x-error-codes:
      - FILE_TOO_LARGE
      - UNSUPPORTED_MEDIA_TYPE
      - FILE_CHECKSUM_MISMATCH
      - INVALID_KML
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FileUploadRequest'
            example:
              fileName: crop-840001.jpg
              mimeType: image/jpeg
              sizeBytes: 48213
              sha256: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
              kind: PHOTO_CROP
              clientId: 7a6b5c4d-0000-4000-8000-000000000226
              ownerClientId: 5b8e3c4a-0000-4000-8000-000000008193
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/FileUploadForm'
            encoding:
              metadata:
                contentType: application/json
      responses:
        '201':
          description: Upload created (PENDING) or completed (multipart).
          headers:
            Location:
              $ref: '#/components/headers/Location'
            Upload-Offset:
              $ref: '#/components/headers/UploadOffset'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileUpload'
              example:
                fileId: 7a6b5c4d-0000-4000-8000-000000000226
                status: PENDING
                uploadOffset: 0
                sizeBytes: 48213
                uploadUrl: https://api.example.invalid/gateway/urvi-app-service/api/v2/files/7a6b5c4d-0000-4000-8000-000000000226
                expiresAt: '2025-10-21T09:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: Same `clientId` with different size or checksum.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/file-checksum-mismatch
                title: File differs
                status: 409
                detail: An upload with this id exists with a different checksum.
                code: FILE_CHECKSUM_MISMATCH
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '413':
          description: File too large.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/file-too-large
                title: File too large
                status: 413
                detail: Files may be at most 20 MB.
                code: FILE_TOO_LARGE
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '415':
          description: Media type not allowed for this kind.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/unsupported-media-type
                title: Unsupported file type
                status: 415
                detail: KML_GEOFENCE accepts KML files only.
                code: UNSUPPORTED_MEDIA_TYPE
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /files/{fileId}:
    patch:
      operationId: appendFileChunk
      tags:
      - Files
      summary: Append a chunk to a resumable upload
      description: |-
        **Use case.** Called by the outbox worker to send file bytes in chunks (e.g. 256 KB on 2G/3G) starting at the server's `Upload-Offset`. After a dropped connection the worker calls `HEAD /files/{fileId}` and resumes. When the last chunk lands the server verifies `sha256` and marks the file COMPLETE.

        **Rules.**
        - `Upload-Offset` must equal the current server offset (`UPLOAD_OFFSET_MISMATCH`, 409).
        - Checksum mismatch on completion -> 422 `FILE_CHECKSUM_MISMATCH`; restart the upload.

        **Error codes.** `UPLOAD_OFFSET_MISMATCH`, `FILE_CHECKSUM_MISMATCH`, `FILE_NOT_FOUND`
      x-replaces: []
      x-features:
      - add-edit-farmer-ie-profile
      - land-assets
      - leased-land
      - geofencing
      - crop-actual
      - atm-model
      - atm-harvest
      - crop-actual-harvest
      - certification-survey
      - activity-tracker
      - vo-shg-farmer-endorsement
      - vo-shg-surveys
      - device-registration
      - pending-sync
      x-error-codes:
      - UPLOAD_OFFSET_MISMATCH
      - FILE_CHECKSUM_MISMATCH
      - FILE_NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FileId'
      - $ref: '#/components/parameters/UploadOffsetReq'
      requestBody:
        required: true
        content:
          application/offset+octet-stream:
            schema:
              type: string
              contentMediaType: application/octet-stream
      responses:
        '204':
          description: Chunk stored.
          headers:
            Upload-Offset:
              $ref: '#/components/headers/UploadOffset'
            Upload-Status:
              $ref: '#/components/headers/UploadStatus'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Offset mismatch; HEAD and resume.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/upload-offset-mismatch
                title: Offset mismatch
                status: 409
                detail: Expected offset 262144.
                code: UPLOAD_OFFSET_MISMATCH
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                context:
                  expectedOffset: 262144
        '422':
          description: Checksum mismatch.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/file-checksum-mismatch
                title: Checksum mismatch
                status: 422
                detail: The uploaded bytes do not match the declared sha256.
                code: FILE_CHECKSUM_MISMATCH
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
    head:
      operationId: getFileUploadOffset
      tags:
      - Files
      summary: Current offset of a resumable upload
      description: |-
        **Use case.** Called by the outbox worker before resuming an interrupted upload to learn how many bytes the server has.

        **Error codes.** `FILE_NOT_FOUND`
      x-replaces: []
      x-features:
      - add-edit-farmer-ie-profile
      - land-assets
      - leased-land
      - geofencing
      - crop-actual
      - atm-model
      - atm-harvest
      - crop-actual-harvest
      - certification-survey
      - activity-tracker
      - vo-shg-farmer-endorsement
      - vo-shg-surveys
      - device-registration
      - pending-sync
      x-error-codes:
      - FILE_NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FileId'
      responses:
        '200':
          description: Offset headers, no body.
          headers:
            Upload-Offset:
              $ref: '#/components/headers/UploadOffset'
            Upload-Length:
              $ref: '#/components/headers/UploadLength'
            Upload-Status:
              $ref: '#/components/headers/UploadStatus'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
    get:
      operationId: getFile
      tags:
      - Files
      summary: File metadata and signed download URL
      description: |-
        **Use case.** Called when the app needs a file it does not have locally: the KML of a server geofence before editing it, a crop / ATM photo after reinstall, or an endorsed abstract. Returns a short-lived signed URL; the app downloads it directly.

        **Rules.**
        - Only files of entities in the user's scope.

        **Error codes.** `FILE_NOT_FOUND`, `FILE_NOT_COMPLETE`
      x-replaces: []
      x-features:
      - geofencing
      - land-assets
      - crop-actual
      - atm-model
      - vo-shg-farmer-endorsement
      x-error-codes:
      - FILE_NOT_FOUND
      - FILE_NOT_COMPLETE
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/FileId'
      responses:
        '200':
          description: File info.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileInfo'
              example:
                fileId: 7a6b5c4d-0000-4000-8000-000000000225
                kind: KML_GEOFENCE
                mimeType: application/vnd.google-earth.kml+xml
                sizeBytes: 2048
                sha256: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
                status: COMPLETE
                url: https://files.example.invalid/urvi/signed/land-1001.kml
                urlExpiresAt: '2025-10-14T10:30:00+05:30'
                createdAt: '2025-10-14T09:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /devices/{deviceId}:
    put:
      operationId: registerDevice
      tags:
      - Devices
      summary: Register or update this device
      description: |-
        **Use case.** Called after login, on every app update and when device details change. Registers the app-scoped device for the user (status LIVE) with brand, model, OS and app version and, optionally, the FCM token. When the tenant enforces the one-device rule and another device is bound, returns 409 `DEVICE_BINDING_CONFLICT` with the other device; the app shows the conflict dialog (Check Sync / Login here / Logout).

        **Rules.**
        - `deviceId` must equal `X-Device-Id` and the token's device claim.

        **Error codes.** `DEVICE_BINDING_CONFLICT`, `DEVICE_BLOCKED`
      x-replaces:
      - POST /gateway/urvi-app-service/api/v1/user-device-session
      x-features:
      - device-registration
      - startup-routing
      x-error-codes:
      - DEVICE_BINDING_CONFLICT
      - DEVICE_BLOCKED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/DeviceIdPath'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceRegistration'
            example:
              brand: BrandA
              model: ModelA
              osVersion: '14'
              appVersion: 2.0.0+200
              pushToken: fcm-token-EXAMPLE-0001
              locale: te
      responses:
        '200':
          description: Registered.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Device'
              example:
                deviceId: 6b1f2c3d-0000-4000-8000-000000053249
                brand: BrandA
                model: ModelA
                osVersion: '14'
                appVersion: 2.0.0+200
                status: LIVE
                boundAt: '2025-06-01T08:00:00+05:30'
                lastSeenAt: '2025-10-14T09:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: Another device is bound.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/device-binding-conflict
                title: Device session conflict
                status: 409
                detail: You are logged in on another device.
                code: DEVICE_BINDING_CONFLICT
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
                context:
                  otherDevice:
                    deviceId: 6b1f2c3d-0000-4000-8000-000000053250
                    brand: BrandB
                    model: ModelB
                    osVersion: '13'
                    appVersion: 2.0.0+200
                    status: LIVE
                    boundAt: '2025-10-10T08:00:00+05:30'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /devices/{deviceId}/push-token:
    put:
      operationId: updatePushToken
      tags:
      - Devices
      summary: Store or rotate the FCM push token
      description: |-
        **Use case.** Called by the FCM service when a token is issued or rotated. The App Service stores it against the user and device and fans out targeted data pushes (endorsement status to the farmer's cadre, comments, remote actions) using FCM HTTP v1 with ids only. Replaces client-side topic subscriptions and client-sent pushes.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - 'SDK FCM topic subscribe: urvi_endorsement_status, urvi_endorsement_comment, NOTIFICATION'
      x-features:
      - notifications
      - device-registration
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/DeviceIdPath'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PushTokenRequest'
            example:
              pushToken: fcm-token-EXAMPLE-0002
              provider: FCM
      responses:
        '204':
          description: Stored.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /devices/{deviceId}/binding/takeover:
    post:
      operationId: takeOverDeviceBinding
      tags:
      - Devices
      summary: '"Login here": move the binding to this device'
      description: |-
        **Use case.** Called from the device-conflict dialog when the user taps Login here (shown only when the tenant allows it). Expires the other device's binding with remark `BINDING_REMOVED`, revokes its refresh tokens and binds this device; the other device receives a LOGOUT remote action.

        **Rules.**
        - Tenant must allow takeover (`BINDING_TAKEOVER_NOT_ALLOWED`, 403).

        **Error codes.** `BINDING_TAKEOVER_NOT_ALLOWED`
      x-replaces:
      - PATCH /gateway/urvi-app-service/api/v1/user-device-sessions/users/{userId}?remarks=
      x-features:
      - device-registration
      x-error-codes:
      - BINDING_TAKEOVER_NOT_ALLOWED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/DeviceIdPath'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BindingTakeoverRequest'
            example:
              confirm: true
              reason: BINDING_REMOVED
      responses:
        '200':
          description: Now bound here.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Device'
              example:
                deviceId: 6b1f2c3d-0000-4000-8000-000000053249
                brand: BrandA
                model: ModelA
                osVersion: '14'
                appVersion: 2.0.0+200
                status: LIVE
                boundAt: '2025-06-01T08:00:00+05:30'
                lastSeenAt: '2025-10-14T09:30:00+05:30'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Takeover not allowed for this tenant.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/binding-takeover-not-allowed
                title: Not allowed
                status: 403
                detail: Ask your administrator to remove the other device.
                code: BINDING_TAKEOVER_NOT_ALLOWED
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /devices/{deviceId}/actions:
    get:
      operationId: listDeviceActions
      tags:
      - Devices
      summary: Pending remote actions for this device
      description: |-
        **Use case.** Called on Home start, on a REMOTE_ACTION push, and as part of bootstrap. The app shows a full-screen lock, displays each action's message with OK and executes it (LOGOUT, CLEAR_APP_DATA, FORCE_SYNC), draining the outbox first when `drainOutboxFirst` is set.

        **Error codes.** `NOT_FOUND`
      x-replaces:
      - GET /gateway/urvi-app-service/api/v1/remote-app-actions/actor
      x-features:
      - device-registration
      x-error-codes:
      - NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/DeviceIdPath'
      responses:
        '200':
          description: Pending actions.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoteActionList'
              example:
                items:
                - id: 88001
                  type: LOGOUT
                  message:
                    en: Your administrator signed you out.
                    te: Your administrator signed you out. (te)
                  createdAt: '2025-10-14T09:30:00+05:30'
                  drainOutboxFirst: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /devices/{deviceId}/actions/{actionId}/complete:
    post:
      operationId: completeDeviceAction
      tags:
      - Devices
      summary: Report a remote action as executed
      description: |-
        **Use case.** Called right after executing a remote action (before wiping for CLEAR_APP_DATA). Optionally references an encrypted, redacted diagnostics bundle uploaded via `POST /files` (kind DIAGNOSTICS) — never a raw database zip.

        **Rules.**
        - Completing twice returns 409 `ACTION_ALREADY_COMPLETED` (treat as success).

        **Error codes.** `ACTION_ALREADY_COMPLETED`, `FILE_NOT_FOUND`
      x-replaces:
      - PATCH /gateway/urvi-app-service/api/v1/remote-app-actions/{id}/execution
      - SDK Firestore / Storage DB zip dump
      x-features:
      - device-registration
      x-error-codes:
      - ACTION_ALREADY_COMPLETED
      - FILE_NOT_FOUND
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/DeviceIdPath'
      - $ref: '#/components/parameters/ActionId'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActionCompletion'
            example:
              status: COMPLETED
              executedAt: '2025-10-14T09:30:00+05:30'
              pendingOpsAtExecution: 0
      responses:
        '204':
          description: Recorded.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Already completed.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/action-already-completed
                title: Already completed
                status: 409
                detail: This action was already reported.
                code: ACTION_ALREADY_COMPLETED
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /telemetry:
    post:
      operationId: postTelemetry
      tags:
      - Telemetry
      summary: Screen events and sync-health counters (no PII)
      description: |-
        **Use case.** Called by a low-priority worker (batched, every few hours on unmetered or any network) with screen views, actions, error codes and sync-health counters (pending ops, failed ops, oldest pending age, conflict / rejection counts). Replaces the raw queue upload, clickstream and Firestore logs. The server drops events that look like PII.

        **Rules.**
        - Strict schema (`additionalProperties: false`); free text is not accepted.
        - Events resembling names, phone numbers or Aadhaar are dropped and counted in `dropped`; a batch made only of such events -> 422 `TELEMETRY_PII_DETECTED`.

        **Error codes.** `TELEMETRY_PII_DETECTED`
      x-replaces:
      - POST /sync-service/api/v1/data/so
      - POST /sync-service/api/v1/data/clickstream
      - SDK Firestore SYNC_LOG / PLI_IMAGE / URVI_ATTACHMENT_ISSUE
      x-features:
      - sync-error-log
      - pending-sync
      - home-navigation
      - settings
      x-error-codes:
      - TELEMETRY_PII_DETECTED
      parameters:
      - $ref: '#/components/parameters/AppVersion'
      - $ref: '#/components/parameters/DeviceId'
      - $ref: '#/components/parameters/RequestId'
      - $ref: '#/components/parameters/AcceptLanguage'
      - $ref: '#/components/parameters/Tenant'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TelemetryBatch'
            example:
              sessionId: d36e0bc1-0000-4000-8000-000000000001
              events:
              - type: screen_view
                at: '2025-10-14T09:30:00+05:30'
                screen: crop_actual_list
                durationMs: 5400
              - type: sync_health
                at: '2025-10-14T09:31:02+05:30'
                counters:
                  pendingOps: 3
                  failedOps: 1
                  oldestPendingAgeMinutes: 95
              - type: error
                at: '2025-10-14T09:31:02+05:30'
                screen: crop_actual_edit
                errorCode: PART_OF_COMPACT_BLOCK
      responses:
        '202':
          description: Accepted.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TelemetryAck'
              example:
                accepted: 3
                dropped: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Only PII-like events.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                type: https://urvi.example.invalid/problems/telemetry-pii-detected
                title: Rejected
                status: 422
                detail: Telemetry must not contain personal data.
                code: TELEMETRY_PII_DETECTED
                requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
        '426':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: '`Authorization: Bearer <accessToken>` from `/auth/login` or `/auth/refresh`. ~15 min; claims: user, tenant,
        roles, device.'
  parameters:
    AppVersion:
      name: X-App-Version
      in: header
      required: true
      description: App version `major.minor.patch+build`. Too old -> 426 `VERSION_TOO_OLD`.
      schema:
        type: string
        pattern: ^\d+\.\d+\.\d+(\+\d+)?$
      example: 2.0.0+200
    DeviceId:
      name: X-Device-Id
      in: header
      required: true
      description: App-scoped device UUID (not ANDROID_ID).
      schema:
        type: string
        format: uuid
      example: 6b1f2c3d-0000-4000-8000-00000000d001
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      description: Client-generated request id for tracing; echoed in responses and problems.
      schema:
        type: string
        format: uuid
      example: 0e0c5a1b-0000-4000-8000-0000000000a1
    Tenant:
      name: X-Tenant
      in: header
      required: false
      description: 'Tenant code; must match the token. Urvi: `ap.gov`.'
      schema:
        type: string
        enum:
        - ap.gov
      example: ap.gov
    AcceptLanguage:
      name: Accept-Language
      in: header
      required: false
      description: Language for messages and labels.
      schema:
        type: string
        enum:
        - en
        - te
        - ta
        default: en
      example: te
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: UUID per write request; retained >= 30 days. Reusing a key with a different body -> 422 `IDEMPOTENCY_KEY_REUSED`.
      schema:
        type: string
        format: uuid
      example: 9c8b7a6d-0000-4000-8000-0000000000b1
    IfNoneMatch:
      name: If-None-Match
      in: header
      required: false
      description: ETag from a previous response; 304 when unchanged.
      schema:
        type: string
      example: '"cfg-2025-26-v41"'
    Cursor:
      name: cursor
      in: query
      required: false
      description: Opaque cursor from the previous page.
      schema:
        type: string
        maxLength: 512
      example: eyJwIjoyfQ
    PageSize:
      name: pageSize
      in: query
      required: false
      description: Items per page.
      schema:
        type: integer
        minimum: 1
        maximum: 500
        default: 100
      example: 100
    Season:
      name: season
      in: query
      required: false
      description: '`current`, `previous` or an explicit season like `2025-26`; the server maps it to year-specific datasets.'
      schema:
        type: string
        pattern: ^(current|previous|\d{4}-\d{2})$
        default: current
      example: current
    FarmerIdPath:
      name: farmerId
      in: path
      required: true
      description: Farmer server id or client UUID (`aliasId`). With a UUID unknown to the server, the farmer is created from
        the flat master record (get-or-create).
      schema:
        type: string
        minLength: 1
        maxLength: 64
      example: 3f6c1a2e-0000-4000-8000-00000000f001
    DeviceIdPath:
      name: deviceId
      in: path
      required: true
      description: App-scoped device UUID; must equal `X-Device-Id`.
      schema:
        type: string
        format: uuid
      example: 6b1f2c3d-0000-4000-8000-00000000d001
    ActionId:
      name: actionId
      in: path
      required: true
      description: Remote action id.
      schema:
        type: integer
        format: int64
      example: 88001
    BatchId:
      name: batchId
      in: path
      required: true
      description: Client batch UUID.
      schema:
        type: string
        format: uuid
      example: 5d4c3b2a-0000-4000-8000-0000000000c1
    FileId:
      name: fileId
      in: path
      required: true
      description: File id from `POST /files`.
      schema:
        type: string
        format: uuid
      example: 7a6b5c4d-0000-4000-8000-0000000000e1
    EndorsementEntityTypePath:
      name: entityType
      in: path
      required: true
      description: Endorsed entity type.
      schema:
        $ref: '#/components/schemas/EndorsementEntityType'
      example: SHG
    EndorsementEntityIdPath:
      name: entityId
      in: path
      required: true
      description: Server id of the VO, SHG or farmer.
      schema:
        type: integer
        format: int64
      example: 52001
    VoIdPath:
      name: voId
      in: path
      required: true
      description: VO server id.
      schema:
        type: integer
        format: int64
      example: 4101
    ShgIdPath:
      name: shgId
      in: path
      required: true
      description: SHG server id.
      schema:
        type: integer
        format: int64
      example: 52001
    UploadOffsetReq:
      name: Upload-Offset
      in: header
      required: true
      description: Byte offset of this chunk; must equal the server's current offset.
      schema:
        type: integer
        minimum: 0
      example: 262144
  headers:
    ETag:
      description: Entity tag of the representation.
      schema:
        type: string
      example: '"cfg-2025-26-v41"'
    CacheControl:
      description: Caching directives.
      schema:
        type: string
      example: private, max-age=300
    XRequestId:
      description: Echo of the request id (or a server-generated one).
      schema:
        type: string
        format: uuid
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 0
      example: 30
    Location:
      description: URL of the created or status resource.
      schema:
        type: string
        format: uri-reference
    UploadOffset:
      description: Bytes received so far.
      schema:
        type: integer
        minimum: 0
      example: 262144
    UploadLength:
      description: Declared total size in bytes.
      schema:
        type: integer
        minimum: 1
      example: 48213
    UploadStatus:
      description: PENDING or COMPLETE.
      schema:
        type: string
        enum:
        - PENDING
        - COMPLETE
  responses:
    BadRequest:
      description: Malformed request or schema violation.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/validation-failed
            title: Validation failed
            status: 400
            detail: One or more fields are invalid.
            code: VALIDATION_FAILED
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
            errors:
            - field: /mobile
              code: INVALID_MOBILE
              message: Mobile must be 10 digits starting 6-9.
    Unauthorized:
      description: Missing, invalid or expired access token. Refresh once, then ask the user to log in; pending outbox ops
        stay intact.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/token-expired
            title: Session expired
            status: 401
            detail: The access token has expired.
            code: TOKEN_EXPIRED
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
    Forbidden:
      description: Authenticated but not allowed (role, tenant, device binding or feature switch).
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/role-not-allowed
            title: Not allowed
            status: 403
            detail: Your role cannot perform this action.
            code: ROLE_NOT_ALLOWED
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
    NotFound:
      description: Resource not found or outside the user's work nodes.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/not-found
            title: Not found
            status: 404
            detail: No such resource in your work area.
            code: NOT_FOUND
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
    Gone:
      description: Cursor expired; download a fresh snapshot.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/cursor-expired
            title: Cursor expired
            status: 410
            detail: The change cursor is older than the retention window. Download a new snapshot.
            code: CURSOR_EXPIRED
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
    UpgradeRequired:
      description: App version below the minimum; block the UI with an update prompt. Pending outbox ops are kept.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/version-too-old
            title: Update required
            status: 426
            detail: Please update Urvi to continue.
            code: VERSION_TOO_OLD
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
            minAppVersion: 2.0.0
    TooManyRequests:
      description: Rate limited per device / user. Respect `Retry-After`.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/rate-limited
            title: Too many requests
            status: 429
            detail: Slow down and retry later.
            code: RATE_LIMITED
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
            retryAfterSeconds: 30
    ServerError:
      description: Unexpected server error; retry with backoff (writes are idempotent).
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://urvi.example.invalid/problems/internal-error
            title: Server error
            status: 500
            detail: Something went wrong. Please try again.
            code: INTERNAL_ERROR
            requestId: 0e0c5a1b-0000-4000-8000-0000000000a1
    NotModified:
      description: Unchanged since the supplied ETag; use the cached copy.
      headers:
        ETag:
          $ref: '#/components/headers/ETag'
  schemas:
    ProblemCode:
      type: string
      description: Typed, stable error code. Clients branch on this, never on `title` or `detail`. Codes are additive within
        v2.
      enum:
      - VALIDATION_FAILED
      - MALFORMED_REQUEST
      - UNAUTHENTICATED
      - TOKEN_EXPIRED
      - INVALID_CREDENTIALS
      - REFRESH_TOKEN_INVALID
      - TENANT_NOT_FOUND
      - FORBIDDEN
      - ROLE_NOT_ALLOWED
      - FEATURE_DISABLED
      - NOT_FOUND
      - VERSION_TOO_OLD
      - RATE_LIMITED
      - INTERNAL_ERROR
      - SERVICE_UNAVAILABLE
      - IDEMPOTENCY_KEY_REUSED
      - PRECONDITION_FAILED
      - STALE_VERSION
      - CURSOR_EXPIRED
      - BATCH_TOO_LARGE
      - DEPENDENCY_FAILED
      - UNKNOWN_REFERENCE
      - LOCKED_FIELD
      - DEVICE_BINDING_CONFLICT
      - BINDING_TAKEOVER_NOT_ALLOWED
      - DEVICE_BLOCKED
      - ACTION_ALREADY_COMPLETED
      - DUPLICATE_FARMER
      - INVALID_AADHAAR
      - INVALID_MOBILE
      - AGE_OUT_OF_RANGE
      - SHG_REQUIRES_FEMALE
      - CATEGORY_FROZEN
      - CATEGORY_REQUIRED
      - CATEGORY_NOT_ALLOWED
      - FARMER_INACTIVE
      - FARMER_EDIT_DISABLED
      - CONSENT_REQUIRED
      - REASON_REQUIRED
      - LAND_AREA_EXCEEDED
      - LAND_ACRES_BELOW_USAGE
      - LAND_AUTHENTICATED_LOCKED
      - LAND_DISABLED
      - LAND_IN_USE
      - LAND_CHECKSUM_MISMATCH
      - LAND_NOT_AUTHENTICATED
      - LEASE_LIMIT_EXCEEDED
      - LESSOR_IS_SELF
      - KHATA_REQUIRED
      - SURVEY_REQUIRED
      - POLYGON_TOO_FEW_POINTS
      - POLYGON_NOT_CLOSED
      - POLYGON_SELF_INTERSECTING
      - GEOFENCE_ACCURACY_TOO_LOW
      - GEOFENCE_ZERO_ACRE_LAND
      - INVALID_KML
      - CROP_TYPE_DISABLED
      - A_GRADE_CROP_COUNT
      - A_GRADE_ASSOCIATED_CROPS_MIN
      - PMDS_SEED_VARIETIES_RANGE
      - PMDS_SEED_QUANTITY_RANGE
      - PRACTICE_REQUIRED
      - PART_OF_COMPACT_BLOCK
      - CROP_HARVESTED_LOCKED
      - SOWING_DATE_OUT_OF_RANGE
      - EDIT_REASON_REQUIRED
      - FMT_EDIT_DISABLED
      - DUPLICATE_TILE
      - HARVEST_SURPLUS_EXCEEDS_TOTAL
      - HARVEST_DATE_INVALID
      - HARVEST_TYPE_DISABLED
      - HARVEST_PHOTO_REQUIRED
      - HARVEST_ALREADY_COMPLETED
      - ATM_C5_AREA_EXCEEDED
      - ATM_C5_LIMIT_ONE
      - ATM_LAND_REQUIRED
      - KAP_DISABLED
      - KAP_LOCKED_ENDORSED
      - KAP_VALUE_EXCEEDS_LIMIT
      - KAP_PMDS_YEARS_REQUIRED
      - KAP_DGC_SEASON_REQUIRED
      - ENDORSEMENT_ORDER
      - ENDORSEMENT_TRANSITION_INVALID
      - ENDORSEMENT_KAP_NOT_SUBMITTED
      - ENDORSEMENT_CATEGORY_NOT_FROZEN
      - ENDORSEMENT_FARMER_NOT_REGISTERED
      - COMMENT_EMPTY
      - COMPACT_BLOCK_BELOW_5_ACRES
      - COMPACT_BLOCK_NAME_TAKEN
      - TILE_NOT_ELIGIBLE
      - TILE_IN_OTHER_BLOCK
      - TILE_NO_GEOFENCE
      - ASSESSMENT_INCOMPLETE
      - SIGNATURE_REQUIRED
      - ACTIVITY_SUBMIT_TOO_EARLY
      - ACTIVITY_ALREADY_SUBMITTED
      - ACTIVITY_EDIT_WINDOW_CLOSED
      - FUTURE_DATE
      - FILE_TOO_LARGE
      - UNSUPPORTED_MEDIA_TYPE
      - UPLOAD_OFFSET_MISMATCH
      - FILE_CHECKSUM_MISMATCH
      - FILE_NOT_FOUND
      - FILE_NOT_COMPLETE
      - TELEMETRY_PII_DETECTED
    FieldError:
      type: object
      required:
      - field
      - code
      properties:
        field:
          type: string
          description: JSON Pointer to the offending field, relative to the request body or the op payload.
          examples:
          - /payload/acres
        code:
          $ref: '#/components/schemas/ProblemCode'
        message:
          type: string
          description: Human-readable message in the request's `Accept-Language`.
        limit:
          type: number
          format: double
          description: The numeric limit that was broken, when relevant (e.g. available acres).
    Problem:
      type: object
      description: RFC 9457 problem details with a typed `code`. Media type `application/problem+json`.
      required:
      - type
      - title
      - status
      - code
      properties:
        type:
          type: string
          description: URI identifying the problem type.
          format: uri
          examples:
          - https://urvi.example.invalid/problems/land-area-exceeded
        title:
          type: string
          description: Short, human-readable summary (localised).
        status:
          type: integer
          description: HTTP status code.
          minimum: 100
          maximum: 599
        detail:
          type: string
          description: Human-readable explanation specific to this occurrence (localised).
        instance:
          type: string
          description: URI reference for this occurrence (usually the request path).
        code:
          $ref: '#/components/schemas/ProblemCode'
        requestId:
          type: string
          description: Echo of `X-Request-Id`, for support.
          format: uuid
        errors:
          type: array
          items:
            $ref: '#/components/schemas/FieldError'
          description: Field-level errors (validation).
        minAppVersion:
          type: string
          description: 'Only for `VERSION_TOO_OLD`: the minimum supported app version.'
          examples:
          - 2.0.0
        retryAfterSeconds:
          type: integer
          description: Only for `RATE_LIMITED` / `SERVICE_UNAVAILABLE`.
          minimum: 0
        context:
          type: object
          description: 'Machine-readable extra data for the code, e.g. `{"availableAcres": 1.25}` or the list of SHGs still
            pending endorsement.'
          properties: {}
          additionalProperties: true
    LocalizedText:
      type: object
      description: Label in every supported language; clients fall back to `en`.
      required:
      - en
      properties:
        en:
          type: string
          description: English.
        te:
          type: string
          description: Telugu.
        ta:
          type: string
          description: Tamil.
    RefOption:
      type: object
      description: Generic dropdown option.
      required:
      - code
      - label
      properties:
        code:
          type: string
          description: Stable code stored in data.
        label:
          $ref: '#/components/schemas/LocalizedText'
        order:
          type: integer
          description: Display order.
          minimum: 0
        group:
          type: string
          description: Optional grouping, e.g. `ACTIVE` / `INACTIVE` for life-status reasons.
        requiresText:
          type: boolean
          description: True for the `OTHER` option that needs free text.
        active:
          type: boolean
    EntityMeta:
      type: object
      description: Fields shared by every synced entity.
      required:
      - clientId
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
          readOnly: true
        clientId:
          type: string
          description: Client-generated UUID (the app's `appId`/`aliasId`). Stable for the life of the entity and used for
            idempotent creates and parent references.
          format: uuid
        version:
          type: integer
          description: Monotonic server version; send as `baseVersion` on updates.
          format: int64
          minimum: 1
          readOnly: true
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true
        active:
          type: boolean
          description: False once soft-deleted / disabled.
    GeoRef:
      type: object
      description: Lightweight reference to a geography / work node.
      required:
      - id
      - type
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        code:
          type: string
          description: Programme code (e.g. VO or SHG code).
        type:
          $ref: '#/components/schemas/GeoNodeType'
        name:
          type: string
          description: Name in the request language.
    UserRef:
      type: object
      required:
      - userId
      properties:
        userId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        displayName:
          type: string
          description: First name only (no surname, phone or email).
        roleCode:
          type: string
    FarmerRef:
      type: object
      required:
      - clientId
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        clientId:
          type: string
          format: uuid
        ieId:
          type: string
          description: Server IE id.
        name:
          type: string
    GeoPoint:
      type: object
      required:
      - lat
      - lng
      properties:
        lat:
          type: number
          format: double
          description: Latitude, WGS84.
          minimum: -90
          maximum: 90
        lng:
          type: number
          format: double
          description: Longitude, WGS84.
          minimum: -180
          maximum: 180
        accuracyM:
          type: number
          format: double
          description: Horizontal accuracy in metres.
          minimum: 0
        capturedAt:
          type: string
          format: date-time
    Polygon:
      type: object
      description: 'GeoJSON Polygon (RFC 7946). Rule: at least 3 distinct vertices (`POLYGON_TOO_FEW_POINTS`), ring closed
        (`POLYGON_NOT_CLOSED`), not self-intersecting (`POLYGON_SELF_INTERSECTING`).'
      required:
      - type
      - coordinates
      properties:
        type:
          type: string
          const: Polygon
        coordinates:
          type: array
          items:
            type: array
            items:
              type: array
              items:
                type: number
                format: double
              minItems: 2
              maxItems: 3
              description: '[longitude, latitude] (GeoJSON order)'
            minItems: 4
            description: 'Linear ring: at least 3 distinct points plus the closing point (first == last).'
          minItems: 1
          maxItems: 1
          description: Exactly one outer ring; holes are not supported.
    Geofence:
      type: object
      description: Boundary of a land parcel, crop block or ATM plot. Send `polygon` or `kmlFileId` (or both).
      properties:
        polygon:
          $ref: '#/components/schemas/Polygon'
        kmlFileId:
          type: string
          description: Optional original KML upload (`POST /files`, kind `KML_GEOFENCE`). If only a KML is sent, the server
            derives `polygon`.
          format: uuid
        captureMethod:
          type: string
          enum:
          - OSM_FIXED
          - OSM_MOTION
          - OSM_TAP
          - GOOGLE_EARTH
          - KML_UPLOAD
          - COPIED_FROM_LAND
        accuracyM:
          type: number
          format: double
          description: Worst GPS accuracy among captured points, metres. Rejected above `formLimits.geofence.maxAccuracyM`
            (`GEOFENCE_ACCURACY_TOO_LOW`).
          minimum: 0
        pointCount:
          type: integer
          minimum: 3
        capturedAt:
          type: string
          format: date-time
        areaAcres:
          type: number
          format: double
          description: 'Server-computed polygon area. Unit: acres.'
          minimum: 0
          readOnly: true
        kmlUrl:
          type: string
          description: Signed download URL of the KML.
          format: uri
          readOnly: true
        sourceOwner:
          type: string
          description: 'When copied: the owner it came from, e.g. `land:<clientId>`.'
    Attachment:
      type: object
      required:
      - fileId
      properties:
        fileId:
          type: string
          description: Id from `POST /files`.
          format: uuid
        kind:
          $ref: '#/components/schemas/FileKind'
        url:
          type: string
          description: Signed URL (short-lived). Images are never inlined.
          format: uri
          readOnly: true
        mimeType:
          type: string
          readOnly: true
        capturedAt:
          type: string
          format: date-time
        location:
          $ref: '#/components/schemas/GeoPoint'
    PageInfo:
      type: object
      required:
      - hasMore
      properties:
        nextCursor:
          type:
          - string
          - 'null'
          description: Opaque cursor for the next page; null on the last page.
        hasMore:
          type: boolean
        pageSize:
          type: integer
          minimum: 1
          maximum: 500
    LoginRequest:
      type: object
      required:
      - login
      - password
      - deviceId
      properties:
        login:
          type: string
          description: 'Lower-cased and trimmed. Accepted shapes: `email`, `login@@tenant`, numeric IE login.'
          minLength: 1
          maxLength: 200
          pattern: ^\S+$
        tenant:
          type: string
          description: Tenant (organisation) name; optional when `login` already contains `@@tenant`. Urvi serves `ap.gov`.
          minLength: 1
          maxLength: 100
        password:
          type: string
          description: Never stored on the device.
          format: password
          minLength: 1
          maxLength: 200
          writeOnly: true
        deviceId:
          type: string
          description: App-scoped device UUID (not ANDROID_ID). Must equal `X-Device-Id`.
          format: uuid
        integrityToken:
          type: string
          description: Optional Play Integrity token.
          writeOnly: true
    RefreshRequest:
      type: object
      required:
      - refreshToken
      - deviceId
      properties:
        refreshToken:
          type: string
          writeOnly: true
          minLength: 20
        deviceId:
          type: string
          format: uuid
    LogoutRequest:
      type: object
      properties:
        refreshToken:
          type: string
          writeOnly: true
        reason:
          type: string
          enum:
          - USER_LOGOUT
          - SESSION_CONFLICT
          - REMOTE_ACTION
          default: USER_LOGOUT
    TokenResponse:
      type: object
      required:
      - accessToken
      - tokenType
      - expiresIn
      - refreshToken
      - refreshExpiresIn
      properties:
        accessToken:
          type: string
          description: 'JWT, ~15 minutes. Claims: user, tenant, roles, device.'
        tokenType:
          type: string
          const: Bearer
        expiresIn:
          type: integer
          description: Seconds until the access token expires.
          minimum: 1
        refreshToken:
          type: string
          description: Rotating, device-bound, ~30 days. Store in Keystore-backed storage.
        refreshExpiresIn:
          type: integer
          minimum: 1
        userId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        tenant:
          type: string
    FlowType:
      type: string
      description: Which app flow the user gets. `UNIT` = Unit In-charge / FMT L1 (role code contains `unit-incharge` or `fmt_l1`);
        `CADRE` otherwise; `FCCC` adds the geographic VO filter.
      enum:
      - CADRE
      - UNIT
      - FCCC
    Permissions:
      type: object
      description: Typed permissions derived from roles on the server.
      properties:
        farmerAddEdit:
          type: boolean
          description: Add / edit farmer profiles (false for HAN users or when the admin switch is off).
        readOnlyProfile:
          type: boolean
          description: 'HAN users: profile and farm modules read-only.'
        endorse:
          type: boolean
          description: Endorse / flag VO, SHG and farmers (Unit).
        resolveFlags:
          type: boolean
          description: Mark flagged items as resolved (Cadre).
        compactBlocks:
          type: boolean
          description: Create and edit compact blocks (Unit / FMT).
        fmtCropEdit:
          type: boolean
          description: Unit / FMT may edit Crop Actual fields other than practices.
        voGeoFilter:
          type: boolean
          description: FCCC geographic filter (role `role.ap.gov.fccc`).
        analytics:
          type: boolean
          description: Analytics tab.
        surveys:
          type: boolean
          description: VO / SHG / unit surveys.
    User:
      type: object
      required:
      - userId
      - flowType
      - permissions
      properties:
        userId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        login:
          type: string
        displayName:
          type: string
          description: First name only.
        roleCodes:
          type: array
          items:
            type: string
          examples:
          - - role.ap.gov.cadre
        flowType:
          $ref: '#/components/schemas/FlowType'
        permissions:
          $ref: '#/components/schemas/Permissions'
        unitIncharge:
          $ref: '#/components/schemas/UserRef'
    Tenant:
      type: object
      required:
      - code
      properties:
        code:
          type: string
          examples:
          - ap.gov
        name:
          type: string
        languages:
          type: array
          items:
            type: string
            enum:
            - en
            - te
            - ta
        timeZone:
          type: string
          examples:
          - Asia/Kolkata
    AppVersionPolicy:
      type: object
      required:
      - minimum
      - recommended
      properties:
        minimum:
          type: string
          description: Below this the API answers 426 `VERSION_TOO_OLD`.
          examples:
          - 2.0.0
        recommended:
          type: string
          description: Below this the app shows a soft update prompt.
        latest:
          type: string
    DeviceBindingStatus:
      type: object
      required:
      - enforced
      - status
      properties:
        enforced:
          type: boolean
          description: Tenant enforces the one-device rule (was `DEVICE_BINDING_EXTERNAL_SWITCH`).
        status:
          type: string
          enum:
          - BOUND_HERE
          - UNBOUND
          - CONFLICT
          - NOT_ENFORCED
        otherDevice:
          $ref: '#/components/schemas/Device'
        canTakeOver:
          type: boolean
          description: Tenant allows "Login here" (was `REMOVE_DEVICE_BINDING_ON_NEW_DEVICE_EXTERNAL_SWITCH`).
    Bootstrap:
      type: object
      required:
      - user
      - tenant
      - workNodes
      - device
      - appVersion
      - configEtag
      - serverTime
      properties:
        user:
          $ref: '#/components/schemas/User'
        tenant:
          $ref: '#/components/schemas/Tenant'
        workNodes:
          type: array
          items:
            $ref: '#/components/schemas/GeoNode'
          description: Work-node tree assigned to the user (districts down to SHGs for Cadre; VOs for Unit).
        device:
          $ref: '#/components/schemas/DeviceBindingStatus'
        pendingActions:
          type: array
          items:
            $ref: '#/components/schemas/RemoteAction'
        appVersion:
          $ref: '#/components/schemas/AppVersionPolicy'
        configEtag:
          type: string
          description: Current ETag of `GET /config`; skip the config call when unchanged.
        serverTime:
          type: string
          description: Use to detect a wrong device clock.
          format: date-time
        currentSeason:
          type: string
          description: Season label, e.g. `2025-26`.
          pattern: ^\d{4}-\d{2}$
    LoginHistoryEntry:
      type: object
      required:
      - loggedInAt
      properties:
        loggedInAt:
          type: string
          format: date-time
        deviceModel:
          type: string
        appVersion:
          type: string
        outcome:
          type: string
          enum:
          - SUCCESS
          - FAILED
    LoginHistoryPage:
      type: object
      required:
      - items
      - page
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/LoginHistoryEntry'
        page:
          $ref: '#/components/schemas/PageInfo'
    Season:
      type: object
      required:
      - current
      - previous
      properties:
        current:
          type: string
          description: Current season, e.g. `2025-26`.
          pattern: ^\d{4}-\d{2}$
        previous:
          type: string
          description: Season label, e.g. `2025-26`.
          pattern: ^\d{4}-\d{2}$
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
        kapSeason:
          type: string
          description: Season the KAP form captures.
          pattern: ^\d{4}-\d{2}$
    FeatureSwitches:
      type: object
      description: Typed feature switches (replace `*_EXTERNAL_SWITCH` ref-data, Remote Config and `versionName_versionCode`
        allow-lists). Unknown future switches may appear; clients ignore them.
      properties:
        farmerAddEdit:
          type: boolean
          description: Farmer add / edit enabled.
        profileConsent:
          type: boolean
          description: Show the consent dialog before adding a farmer.
        multipleCategoryEdits:
          type: boolean
          description: Allow category edits before freeze.
        editAuthenticatedLand:
          type: boolean
          description: Allow editing authenticated land.
        khataMandatory:
          type: boolean
          description: Khata number mandatory for owned land.
        surveyMandatory:
          type: boolean
          description: Survey number mandatory (not for leased-in).
        geofencingAssets:
          type: boolean
          description: Land geofencing.
        geofencingAtm:
          type: boolean
          description: ATM geofencing.
        atm:
          type: boolean
          description: ATM module.
        atmHarvest:
          type: boolean
          description: ATM harvest.
        cropActual:
          type: boolean
          description: Crop Actual module.
        cropActualHarvestContinuous:
          type: boolean
          description: Continuous harvest on Crop Actual.
        cropActualHarvestWhole:
          type: boolean
          description: Whole-crop harvest on Crop Actual.
        pmdsCropType:
          type: boolean
          description: Offer PMDS crop type.
        rdsCropType:
          type: boolean
          description: Offer RDS crop type.
        cropActualCopyForward:
          type: boolean
          description: Copy last month's blocks into the current month.
        copyDisabledCropActual:
          type: boolean
          description: Copy blocks whose land is disabled.
        currentMonthPortfolioOnly:
          type: boolean
          description: Download only line items starting this or last month.
        kap:
          type: boolean
          description: KAP 25-26 entries.
        certification:
          type: boolean
          description: Certification survey.
        activityTracker:
          type: boolean
          description: Activity tracker.
        profile:
          type: boolean
          description: Profile tile.
        land:
          type: boolean
          description: Land tile.
        deviceBinding:
          type: boolean
          description: One-device rule.
        removeBindingOnNewDevice:
          type: boolean
          description: Allow "Login here".
        removeBindingOnLogout:
          type: boolean
          description: Expire the binding on logout.
        showForceSync:
          type: boolean
          description: Force-sync button in Settings.
        showAnalyticsTab:
          type: boolean
          description: Analytics tab.
        helpVideoAutoPopup:
          type: boolean
          description: Auto-open the module help video.
      additionalProperties:
        type: boolean
    GeofenceMethods:
      type: object
      properties:
        osmFixed:
          type: boolean
        osmMotion:
          type: boolean
        osmTap:
          type: boolean
        googleEarth:
          type: boolean
        kmlUpload:
          type: boolean
    SyncSettings:
      type: object
      required:
      - maxOpsPerBatch
      - maxAttempts
      - snapshotPageSize
      properties:
        maxOpsPerBatch:
          type: integer
          minimum: 1
          maximum: 100
          default: 100
        retryBaseSeconds:
          type: integer
          description: Exponential backoff base.
          minimum: 1
        retryMaxSeconds:
          type: integer
          minimum: 1
        maxAttempts:
          type: integer
          description: After this an outbox op is marked `failed` and shown on Pending Sync (never dropped).
          minimum: 1
        snapshotPageSize:
          type: integer
          minimum: 1
          maximum: 500
        setupCacheMinutes:
          type: integer
          description: Skip config / master refresh if fetched within this window.
          minimum: 0
        disabledLandRefreshMinutes:
          type: integer
          minimum: 0
        analyticsRefreshHours:
          type: integer
          minimum: 1
        changesPollMinutes:
          type: integer
          minimum: 1
    FormLimits:
      type: object
      description: Rule parameters shared by client and server so both validate with the same numbers.
      properties:
        farmer:
          type: object
          properties:
            nameMaxLength:
              type: integer
              default: 100
            reasonMaxLength:
              type: integer
              default: 100
            ageMin:
              type: integer
              default: 18
            ageMax:
              type: integer
              default: 110
            lastMandatoryCategoryUpdateDate:
              type: string
              description: Category set after this date counts as frozen.
              format: date
        land:
          type: object
          properties:
            acresMin:
              type: number
              format: double
              default: 0.01
            acresMax:
              type: number
              format: double
              default: 99.99
        livestock:
          type: object
          properties:
            countMax:
              type: integer
              default: 999
        atm:
          type: object
          properties:
            acresMax:
              type: number
              format: double
              default: 99.99
            c5MaxAcres:
              type: number
              format: double
              description: 'C5 members: ATM land at most this many acres.'
              default: 0.5
            c5MaxCount:
              type: integer
              default: 1
        cropBlock:
          type: object
          properties:
            acresMax:
              type: number
              format: double
              default: 99.99
            sowingLookbackMonths:
              type: integer
              default: 6
        pmds:
          type: object
          properties:
            seedVarietiesMin:
              type: integer
              default: 6
            seedVarietiesMax:
              type: integer
              default: 35
            seedQuantityKgMin:
              type: number
              format: double
              default: 1
            seedQuantityKgMax:
              type: number
              format: double
              default: 999
        aGrade:
          type: object
          properties:
            mainCropsMax:
              type: integer
              default: 1
            associatedCropsMin:
              type: integer
              default: 2
            biodiversityCropsMin:
              type: integer
              default: 2
        harvest:
          type: object
          properties:
            cropMaxQuintals:
              type: number
              format: double
              default: 999.99
        compactBlock:
          type: object
          properties:
            minTotalAcres:
              type: number
              format: double
              default: 5
        geofence:
          type: object
          properties:
            minPoints:
              type: integer
              default: 3
            maxAccuracyM:
              type: number
              format: double
            motionMinDistanceM:
              type: number
              format: double
              default: 5
            motionIntervalSeconds:
              type: integer
              default: 2
        activityTracker:
          type: object
          properties:
            submitAfterLocalTime:
              type: string
              pattern: ^\d{2}:\d{2}$
              default: '18:00'
            photoMaxKb:
              type: integer
              default: 45
        kap:
          type: object
          properties:
            extentMax:
              type: number
              format: double
              default: 99.99
    KapFieldDef:
      type: object
      required:
      - code
      - label
      - type
      properties:
        code:
          type: string
          examples:
          - c1Acres
        label:
          $ref: '#/components/schemas/LocalizedText'
        type:
          type: string
          enum:
          - decimal
          - integer
          - choice
          - boolean
          - multiChoice
        min:
          type: number
          format: double
        max:
          type: number
          format: double
        maxOf:
          type: string
          description: Code of another field this one must not exceed in the same season (e.g. `pmdsAcres` <= `c1Acres`).
        options:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        enabled:
          type: boolean
        disabledForCategories:
          type: array
          items:
            $ref: '#/components/schemas/CategoryCode'
    KapFormSchema:
      type: object
      required:
      - season
      - fields
      properties:
        season:
          type: string
          description: Season label, e.g. `2025-26`.
          pattern: ^\d{4}-\d{2}$
        previousSeason:
          type: string
          description: Season label, e.g. `2025-26`.
          pattern: ^\d{4}-\d{2}$
        fields:
          type: array
          items:
            $ref: '#/components/schemas/KapFieldDef'
        pmdsYearOptions:
          type: array
          items:
            type: string
          examples:
          - - 2019-20
            - 2020-21
        dgcSeasonOptions:
          type: array
          items:
            type: string
            enum:
            - PMDS
            - KHARIF
            - RABI
            - RDSS1
            - RDSS2
    Product:
      type: object
      required:
      - code
      - name
      properties:
        code:
          type: string
          description: Product / SKU code.
        name:
          $ref: '#/components/schemas/LocalizedText'
        majorType:
          type: string
          enum:
          - VEGETABLES_LEAFY_TUBERS
          - PULSES
          - CEREALS
          - OILSEEDS
          - HORTICULTURE
          - FODDER
          - OTHER
        categoryCode:
          type: string
        cropTypes:
          type: array
          items:
            $ref: '#/components/schemas/CropType'
          description: Crop types this crop may be picked for.
        isDummy:
          type: boolean
          description: Placeholder crop; never offered in pickers.
    Practice:
      type: object
      required:
      - code
      - name
      properties:
        code:
          type: string
        name:
          $ref: '#/components/schemas/LocalizedText'
        isAGrade:
          type: boolean
        isS2S:
          type: boolean
          description: The S2S practice (`AP-GOV-KAP-100_PERCENT_CHEMICAL_FREE_NATURAL_FARMING`).
        isPmds:
          type: boolean
        isNoNf:
          type: boolean
        isChemicalShare:
          type: boolean
          description: Removed when blocks are copied forward.
    AssetCategory:
      type: object
      required:
      - code
      - landType
      - label
      properties:
        code:
          $ref: '#/components/schemas/LandCategory'
        landType:
          $ref: '#/components/schemas/LandType'
        functionalityCode:
          type: string
        label:
          $ref: '#/components/schemas/LocalizedText'
        khataApplicable:
          type: boolean
        surveyApplicable:
          type: boolean
        acresApplicable:
          type: boolean
        descriptionApplicable:
          type: boolean
    Uom:
      type: object
      required:
      - code
      properties:
        code:
          type: string
          examples:
          - UOM.AREA.ACRE
        symbol:
          type: string
        name:
          $ref: '#/components/schemas/LocalizedText'
    HelpVideo:
      type: object
      required:
      - module
      - url
      properties:
        module:
          type: string
        title:
          $ref: '#/components/schemas/LocalizedText'
        url:
          type: string
          format: uri
        autoPopup:
          type: boolean
    ProgramInstruction:
      type: object
      required:
      - order
      - text
      properties:
        order:
          type: integer
          minimum: 0
        text:
          $ref: '#/components/schemas/LocalizedText'
    ProgramConfigItem:
      type: object
      required:
      - key
      - value
      properties:
        key:
          type: string
        label:
          $ref: '#/components/schemas/LocalizedText'
        value:
          type: string
          description: Display value; `1`/`0` render as True/False.
    Masters:
      type: object
      description: Reference data. Labels carry every language.
      properties:
        products:
          type: array
          items:
            $ref: '#/components/schemas/Product'
        practices:
          type: array
          items:
            $ref: '#/components/schemas/Practice'
        assetCategories:
          type: array
          items:
            $ref: '#/components/schemas/AssetCategory'
        uoms:
          type: array
          items:
            $ref: '#/components/schemas/Uom'
        genders:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        maritalStatuses:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        educationalQualifications:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        socialCategories:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        lifeStatusReasons:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        roles:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        cadreDesignations:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        nfCategories:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        cropTypeChangeReasons:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        cropEditReasons:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        seedVarieties:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        livestockTypes:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        aapRejectReasons:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        landDeleteReasons:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        productCategories:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
    ConfigBundle:
      type: object
      required:
      - version
      - season
      - featureSwitches
      - sync
      - formLimits
      - masters
      - kapFormSchema
      properties:
        version:
          type: string
          description: Bundle version; equals the ETag without quotes.
        generatedAt:
          type: string
          format: date-time
        season:
          $ref: '#/components/schemas/Season'
        featureSwitches:
          $ref: '#/components/schemas/FeatureSwitches'
        geofenceMethods:
          $ref: '#/components/schemas/GeofenceMethods'
        sync:
          $ref: '#/components/schemas/SyncSettings'
        formLimits:
          $ref: '#/components/schemas/FormLimits'
        masters:
          $ref: '#/components/schemas/Masters'
        kapFormSchema:
          $ref: '#/components/schemas/KapFormSchema'
        helpVideos:
          type: array
          items:
            $ref: '#/components/schemas/HelpVideo'
        programInstructions:
          type: array
          items:
            $ref: '#/components/schemas/ProgramInstruction'
        programConfig:
          type: array
          items:
            $ref: '#/components/schemas/ProgramConfigItem'
        homeBanner:
          $ref: '#/components/schemas/LocalizedText'
        notificationWebUrl:
          type: string
          format: uri
    GeoNodeType:
      type: string
      enum:
      - STATE
      - DISTRICT
      - MANDAL
      - VILLAGE
      - PANCHAYAT
      - VO
      - HABITATION
      - SHG
    GeoNode:
      type: object
      required:
      - id
      - type
      - name
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        code:
          type: string
        aliasId:
          type: string
        type:
          $ref: '#/components/schemas/GeoNodeType'
        name:
          $ref: '#/components/schemas/LocalizedText'
        parentId:
          type:
          - integer
          - 'null'
          description: Server id (assigned by the App Service).
          format: int64
        children:
          type: array
          items:
            $ref: '#/components/schemas/GeoNode'
          description: Present only in the bootstrap tree.
    GeoChildrenResponse:
      type: object
      required:
      - level
      - items
      properties:
        parent:
          $ref: '#/components/schemas/GeoRef'
        level:
          $ref: '#/components/schemas/GeoNodeType'
        items:
          type: array
          items:
            $ref: '#/components/schemas/GeoNode'
    CategoryCode:
      type: string
      description: Current member category. `FARMER`; `C4` = non-agricultural member; `C5` = POP / landless kitchen-garden
        member; `NA` = not yet set.
      enum:
      - FARMER
      - C4
      - C5
      - NA
    FarmerIdentity:
      type: object
      properties:
        aadhaar:
          type: string
          description: Write-only. Exactly 12 digits. Never returned.
          pattern: ^\d{12}$
          writeOnly: true
        aadhaarMasked:
          type: string
          description: Masked Aadhaar.
          pattern: ^XXXX-XXXX-\d{4}$
          readOnly: true
        aadhaarSharedWithOther:
          type: boolean
          description: True if another farmer in the tenant has the same Aadhaar (warning only, not blocking).
          readOnly: true
    FarmerFamily:
      type: object
      description: 'Rule: SPOUSE requires name and Aadhaar; FATHER requires name only.'
      required:
      - relation
      - name
      properties:
        relation:
          type: string
          description: SPOUSE when marital status is Married, otherwise FATHER.
          enum:
          - SPOUSE
          - FATHER
        name:
          type: string
          maxLength: 100
          pattern: ^[A-Za-z0-9 ]+$
        identity:
          $ref: '#/components/schemas/FarmerIdentity'
        mobile:
          type: string
          description: 'Indian mobile number: 10 digits starting 6, 7, 8 or 9.'
          pattern: ^[6-9]\d{9}$
    FarmerCategory:
      type: object
      required:
      - code
      properties:
        code:
          $ref: '#/components/schemas/CategoryCode'
        setAt:
          type: string
          description: When the category was last changed (was `gapStartDate`). The server sets it when `code` changes.
          format: date-time
        frozen:
          type: boolean
          description: True when set after `formLimits.farmer.lastMandatoryCategoryUpdateDate`, or the member is FARMER with
            any authenticated land. Frozen categories reject edits with `CATEGORY_FROZEN`.
          readOnly: true
        frozenReason:
          type:
          - string
          - 'null'
          enum:
          - DEADLINE
          - LAND_AUTHENTICATED
          - SUBMITTED_C4_C5
          - ACTIVE_LAND
          - null
          readOnly: true
    FarmerStatus:
      type: object
      required:
      - lifeStatus
      properties:
        lifeStatus:
          type: string
          enum:
          - ACTIVE
          - INACTIVE
        reasonCode:
          type: string
          description: Code from `masters.lifeStatusReasons`; required when `lifeStatus` changes (`REASON_REQUIRED`).
        reasonText:
          type: string
          description: Free text when the reason is OTHER.
          maxLength: 100
    FarmerRole:
      type: object
      required:
      - roleCode
      properties:
        roleCode:
          type: string
          description: From `masters.roles`, e.g. `CADRE`, `MEMBER`, `LEAD_FARMER`.
        cadreDesignation:
          type: string
          description: From `masters.cadreDesignations`; only with `CADRE`.
    Farmer:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: 'Create requires: clientId, name, gender, age, mobile, identity.aadhaar, village/habitation/vo. Updates
          send only changed fields. Server rules: DUPLICATE_FARMER (same name + mobile + gender), CATEGORY_FROZEN, LOCKED_FIELD
          (admin-set life status), FARMER_EDIT_DISABLED.'
        properties:
          ieId:
            type: string
            description: Server IE id.
            readOnly: true
          name:
            type: string
            description: Letters, digits and spaces only.
            minLength: 1
            maxLength: 100
            pattern: ^[A-Za-z0-9 ]+$
          gender:
            type: string
            description: Code from `masters.genders`.
            enum:
            - FEMALE
            - MALE
            - OTHER
          age:
            type: integer
            description: Years.
            minimum: 18
            maximum: 110
          mobile:
            type: string
            description: 'Indian mobile number: 10 digits starting 6, 7, 8 or 9.'
            pattern: ^[6-9]\d{9}$
          identity:
            $ref: '#/components/schemas/FarmerIdentity'
          maritalStatus:
            type: string
            description: Code from `masters.maritalStatuses`.
          family:
            $ref: '#/components/schemas/FarmerFamily'
          education:
            type: string
            description: Code from `masters.educationalQualifications`; required while ACTIVE.
          socialCategory:
            type: string
            description: Code from `masters.socialCategories`; required while ACTIVE.
          village:
            $ref: '#/components/schemas/GeoRef'
          habitation:
            $ref: '#/components/schemas/GeoRef'
          vo:
            $ref: '#/components/schemas/GeoRef'
          shg:
            description: SHG; null = Non-SHG. Only FEMALE farmers may be in an SHG (`SHG_REQUIRES_FEMALE`).
            anyOf:
            - $ref: '#/components/schemas/GeoRef'
            - type: 'null'
          roles:
            type: array
            items:
              $ref: '#/components/schemas/FarmerRole'
            description: Designations; at least one when editing an ACTIVE farmer.
          category:
            $ref: '#/components/schemas/FarmerCategory'
          status:
            $ref: '#/components/schemas/FarmerStatus'
          photo:
            $ref: '#/components/schemas/Attachment'
          interestedInTrade:
            type: boolean
          consentGiven:
            type: boolean
            description: Required true on create when `featureSwitches.profileConsent` (`CONSENT_REQUIRED`).
            writeOnly: true
          nfCategory:
            type: string
            description: Server-computed natural-farming category label for the current season, e.g. `PMDS+C1`.
            readOnly: true
          endorsementStatus:
            $ref: '#/components/schemas/EndorsementStatus'
            readOnly: true
          flags:
            type: object
            properties:
              hasAtm:
                type: boolean
              hasAGrade:
                type: boolean
              hasCompactBlock:
                type: boolean
              profileComplete:
                type: boolean
            readOnly: true
      description: Farmer (IE, Individual Entrepreneur).
    FarmerMetrics:
      type: object
      description: Server-side land usage, same formula as validation. All values in acres.
      required:
      - ownedAcres
      - availableAcres
      - parcels
      properties:
        ownedAcres:
          type: number
          format: double
          description: 'Unit: acres.'
          minimum: 0
        leasedInAcres:
          type: number
          format: double
          minimum: 0
        leasedOutAcres:
          type: number
          format: double
          minimum: 0
        cropActualAcres:
          type: number
          format: double
          description: Current-season, current-month Crop Actual acres (unharvested, latest per tile).
          minimum: 0
        atmAcres:
          type: number
          format: double
          description: Latest version of every ATM.
          minimum: 0
        usedAcres:
          type: number
          format: double
          minimum: 0
        availableAcres:
          type: number
          format: double
        kapSurveyCount:
          type: integer
          minimum: 0
        parcels:
          type: array
          items:
            $ref: '#/components/schemas/ParcelAvailability'
        computedAt:
          type: string
          format: date-time
    ParcelAvailability:
      type: object
      required:
      - landId
      - totalAcres
      - availableAcres
      properties:
        landId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        landClientId:
          type: string
          format: uuid
        surveyNo:
          type: string
        totalAcres:
          type: number
          format: double
          minimum: 0
        leasedOutAcres:
          type: number
          format: double
          minimum: 0
        cropActualAcres:
          type: number
          format: double
          minimum: 0
        atmAcres:
          type: number
          format: double
          minimum: 0
        availableAcres:
          type: number
          format: double
          description: 'totalAcres - leasedOut - cropActual - atm. Unit: acres.'
        disabled:
          type: boolean
    FarmerAggregate:
      type: object
      description: One fully hydrated farmer (attachment URLs, not bytes).
      required:
      - farmer
      properties:
        farmer:
          $ref: '#/components/schemas/Farmer'
        lands:
          type: array
          items:
            $ref: '#/components/schemas/Land'
        livestock:
          type: array
          items:
            $ref: '#/components/schemas/Livestock'
        cropStock:
          type: array
          items:
            $ref: '#/components/schemas/CropStock'
        cropBlocks:
          type: array
          items:
            $ref: '#/components/schemas/CropBlock'
        farmingModels:
          type: array
          items:
            $ref: '#/components/schemas/FarmingModel'
        harvests:
          type: array
          items:
            $ref: '#/components/schemas/Harvest'
        plan:
          anyOf:
          - $ref: '#/components/schemas/KapPlan'
          - type: 'null'
        metrics:
          $ref: '#/components/schemas/FarmerMetrics'
        endorsement:
          anyOf:
          - $ref: '#/components/schemas/Endorsement'
          - type: 'null'
    FarmerSummary:
      type: object
      required:
      - id
      - name
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        clientId:
          type: string
          format: uuid
        ieId:
          type: string
        name:
          type: string
        relativeName:
          type: string
          description: Husband / father name.
        gender:
          type: string
        shg:
          $ref: '#/components/schemas/GeoRef'
        vo:
          $ref: '#/components/schemas/GeoRef'
        category:
          $ref: '#/components/schemas/CategoryCode'
        lifeStatus:
          type: string
          enum:
          - ACTIVE
          - INACTIVE
        kapSubmitted:
          type: boolean
        endorsementStatus:
          $ref: '#/components/schemas/EndorsementStatus'
    FarmerSearchPage:
      type: object
      required:
      - items
      - page
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/FarmerSummary'
        page:
          $ref: '#/components/schemas/PageInfo'
    AbstractField:
      type: object
      required:
      - key
      - value
      properties:
        key:
          type: string
        label:
          $ref: '#/components/schemas/LocalizedText'
        value:
          type: string
        unit:
          type: string
    FarmerAbstract:
      type: object
      required:
      - farmerId
      - season
      - fields
      properties:
        farmerId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        season:
          type: string
          description: Season label, e.g. `2025-26`.
          pattern: ^\d{4}-\d{2}$
        fields:
          type: array
          items:
            $ref: '#/components/schemas/AbstractField'
        asOf:
          type: string
          format: date-time
    LandCategory:
      type: string
      enum:
      - LAND_OWNED
      - LEASED_IN
      - ROFR
      - ENDOWMENT
      - ASSIGNED_D_PATTA
      - LAND_OWNED_OTHERS
      - LEASED_IN_OTHERS
    LandType:
      type: string
      enum:
      - IRRIGATED
      - RAINFED
    LeasedLand:
      type: object
      description: Lease details. One leased-in Land is created per lessor parcel; it copies khata, survey number and geofence.
      required:
      - leaseType
      - lessorLandId
      - leasedAcres
      properties:
        leaseType:
          type: string
          enum:
          - INCOMING
          - OUTGOING
        leaseStatus:
          type: string
          enum:
          - PENDING
          - APPROVED
          - REJECTED
          readOnly: true
        lessorFarmerId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        lessorLandId:
          type: integer
          description: The lessor's parcel.
          format: int64
        lessorWorkNode:
          $ref: '#/components/schemas/GeoRef'
        leasedAcres:
          type: number
          format: double
          description: 'Leased acres; > 0 and <= the lessor parcel''s available acres (`LEASE_LIMIT_EXCEEDED`). Unit: acres.'
          minimum: 0.01
          maximum: 99.99
          multipleOf: 0.01
    Land:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: 'Rules: acres change must stay >= Crop Actual + ATM + leased-out acres (`LAND_ACRES_BELOW_USAGE`); delete
          blocked while unharvested crops, ATM or leases exist (`LAND_IN_USE`).'
        properties:
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmerClientId:
            type: string
            format: uuid
          category:
            $ref: '#/components/schemas/LandCategory'
          landType:
            $ref: '#/components/schemas/LandType'
          ownershipType:
            type: string
            enum:
            - OWNER
            - CULTIVATOR
            description: Only for LAND_OWNED.
          description:
            type: string
            maxLength: 200
          name:
            type: string
            maxLength: 100
          khataNo:
            type: string
            description: Required for LAND_OWNED when `featureSwitches.khataMandatory` (`KHATA_REQUIRED`).
            maxLength: 50
          surveyNo:
            type: string
            description: Required (except leased-in) when `featureSwitches.surveyMandatory` (`SURVEY_REQUIRED`).
            maxLength: 50
          acres:
            type: number
            format: double
            description: 'Parcel area, > 0, at most 2 decimals. For leased-in land equals `lease.leasedAcres`. Unit: acres.'
            minimum: 0.01
            maximum: 99.99
            multipleOf: 0.01
          authenticated:
            type: boolean
            description: Once true and saved, the parcel is locked unless `featureSwitches.editAuthenticatedLand` (`LAND_AUTHENTICATED_LOCKED`).
          disabled:
            type: boolean
            description: Disabled by an administrator; no new crops (`LAND_DISABLED`).
            readOnly: true
          inactiveReason:
            type: string
            description: Delete reason (required on delete).
          lease:
            $ref: '#/components/schemas/LeasedLand'
          leasedTo:
            type: array
            items:
              $ref: '#/components/schemas/FarmerRef'
            readOnly: true
          geofence:
            $ref: '#/components/schemas/Geofence'
          checksum:
            type: string
            description: Server checksum of acres + identifiers; crop blocks and ATMs echo it as `landChecksum` (`LAND_CHECKSUM_MISMATCH`).
            readOnly: true
    Livestock:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        required:
        - animalType
        - count
        properties:
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmerClientId:
            type: string
            format: uuid
          animalType:
            type: string
            enum:
            - ADULT_COW
            - OTHER_COW
            - OXEN_BULL
            - GOAT
            - SHEEP
            - ADULT_BUFFALO
          count:
            type: integer
            description: Head count.
            minimum: 0
            maximum: 999
    CropStock:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        required:
        - productCode
        - quantityQuintals
        properties:
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmerClientId:
            type: string
            format: uuid
          productCode:
            type: string
          quantityQuintals:
            type: number
            format: double
            description: 'Stock held. Unit: quintals.'
            minimum: 0
            maximum: 99999.99
            multipleOf: 0.01
    CropType:
      type: string
      enum:
      - AGRICULTURE
      - HORTICULTURE
      - POLYCROPPING
      - PMDS
      - RDS
      - A_GRADE
      - NO_NF_CROP
    HarvestStatus:
      type: string
      enum:
      - NOT_STARTED
      - PENDING
      - COMPLETED
    HarvestType:
      type: string
      enum:
      - CONTINUOUS
      - WHOLE_CROP
    CropLine:
      type: object
      required:
      - clientId
      - productCode
      - acres
      properties:
        clientId:
          type: string
          format: uuid
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
          readOnly: true
        productCode:
          type: string
        acres:
          type: number
          format: double
          description: 'Crop area within the block, > 0. Unit: acres.'
          minimum: 0.01
          maximum: 99.99
          multipleOf: 0.01
        isMajor:
          type: boolean
        seedVariety:
          type: string
          description: Letters, digits, '-', '()' and space.
          maxLength: 100
          pattern: ^[A-Za-z0-9\-() ]*$
        expectedHarvestDate:
          type: string
          description: Not in the past when set.
          format: date
        harvestStatus:
          $ref: '#/components/schemas/HarvestStatus'
          readOnly: true
        placeholder:
          type: boolean
          description: Placeholder crop created by copy-forward.
          readOnly: true
        editReason:
          type: string
          enum:
          - WRONG_ENTRY
          - CROP_FAILED
          - HARVEST_COMPLETED
          - OTHER
        editReasonText:
          type: string
          maxLength: 100
          pattern: ^[A-Za-z0-9\-() ]*$
        removed:
          type: boolean
          description: Set with `deleteReason` to remove a crop that existed before.
        deleteReason:
          type: string
          maxLength: 100
    PmdsDetails:
      type: object
      required:
      - pmdsType
      - sowingMode
      - seedVarietiesCount
      - seedQuantityKg
      properties:
        pmdsType:
          type: string
          enum:
          - REGULAR
          - PELLETIZED
        kitsPerAcre:
          type: integer
          description: Kits of 15 kg used per acre; only for REGULAR.
          enum:
          - 1
          - 2
        sowingMode:
          type: string
          enum:
          - BROADCASTING
          - LINE_SOWING
        seedVarietiesCount:
          type: integer
          description: Varieties sown (`PMDS_SEED_VARIETIES_RANGE`).
          minimum: 6
          maximum: 35
        seedQuantityKg:
          type: number
          format: double
          description: 'PMDS seed used. Unit: kg (`PMDS_SEED_QUANTITY_RANGE`).'
          minimum: 1
          maximum: 999
          multipleOf: 0.01
    AGradeDetails:
      type: object
      description: 'A Grade: exactly one main crop (`A_GRADE_CROP_COUNT`) and >= 2 associated and bio-diversity crops (`A_GRADE_ASSOCIATED_CROPS_MIN`).'
      required:
      - associatedCropCodes
      - biodiversityCropCodes
      properties:
        associatedCropCodes:
          type: array
          items:
            type: string
          minItems: 2
          uniqueItems: true
        biodiversityCropCodes:
          type: array
          items:
            type: string
          minItems: 2
          uniqueItems: true
    CropChange:
      type: object
      required:
      - kind
      - at
      properties:
        kind:
          type: string
          enum:
          - CROP_ADDED
          - CROP_REMOVED
          - ACRES_CHANGED
          - CROP_TYPE_CHANGED
        productCode:
          type: string
        fromValue:
          type: string
        toValue:
          type: string
        reasonCode:
          type: string
        reasonText:
          type: string
          maxLength: 100
        at:
          type: string
          format: date-time
    CropBlock:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: 'Crop Actual block (portfolio line item). Rules: unharvested acres of all current-season blocks on the
          land <= land acres - ATM acres (`LAND_AREA_EXCEEDED`); land disabled (`LAND_DISABLED`); in a compact block (`PART_OF_COMPACT_BLOCK`);
          crop type disabled (`CROP_TYPE_DISABLED`); harvested crops lock type/additions (`CROP_HARVESTED_LOCKED`).'
        properties:
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmerClientId:
            type: string
            format: uuid
          landId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          landClientId:
            type: string
            description: Land, by client UUID (may be created earlier in the same batch).
            format: uuid
          landChecksum:
            type: string
            description: Checksum of the land when the block was edited; stale -> `LAND_CHECKSUM_MISMATCH`.
          tileId:
            type: string
            description: Stable tile id (was `titleId`) shared by every monthly version of the block.
          planType:
            type: string
            description: SEASON_ACTUAL = Crop Actual (PLANNED is legacy Crop Plan, read-only).
            enum:
            - SEASON_ACTUAL
            - PLANNED
          season:
            type: string
            description: Season label, e.g. `2025-26`.
            pattern: ^\d{4}-\d{2}$
          month:
            type: string
            description: Calendar month, `yyyy-MM`.
            pattern: ^\d{4}-(0[1-9]|1[0-2])$
          cropType:
            $ref: '#/components/schemas/CropType'
          cropTypeChangeReason:
            type: string
            enum:
            - WRONG_ENTRY
            - SOIL_INCORPORATION
            - INTEGRATION_WITH_KHARIF
            - CROP_FAILED
            - HARVEST_COMPLETE
            - OTHER
          cropTypeChangeReasonText:
            type: string
            maxLength: 100
            pattern: ^[A-Za-z0-9\-() ]*$
          crops:
            type: array
            items:
              $ref: '#/components/schemas/CropLine'
            description: Main crops; >= 1. A_GRADE allows exactly 1; PMDS is the `AP_GOV_PMDS` crop.
            minItems: 1
          practiceCodes:
            type: array
            items:
              type: string
            description: NF practices; >= 1 (`PRACTICE_REQUIRED`).
            minItems: 1
            uniqueItems: true
          sowingDate:
            type: string
            description: Between today minus `formLimits.cropBlock.sowingLookbackMonths` and today (`SOWING_DATE_OUT_OF_RANGE`).
            format: date
          pmds:
            $ref: '#/components/schemas/PmdsDetails'
          aGrade:
            $ref: '#/components/schemas/AGradeDetails'
          photo:
            $ref: '#/components/schemas/Attachment'
          geofence:
            $ref: '#/components/schemas/Geofence'
          changeLog:
            type: array
            items:
              $ref: '#/components/schemas/CropChange'
          compactBlockId:
            type:
            - integer
            - 'null'
            description: Server id (assigned by the App Service).
            format: int64
            readOnly: true
          editable:
            type: boolean
            description: False when in a compact block, land disabled or harvested.
            readOnly: true
          totalAcres:
            type: number
            format: double
            description: 'Sum of crop acres. Unit: acres.'
            readOnly: true
    CropHarvest:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: Crop Actual harvest (append-only). WHOLE_CROP requires a photo (`HARVEST_PHOTO_REQUIRED`) and closes
          the crop.
        required:
        - kind
        - cropLineClientId
        - harvestType
        - totalOutputQuintals
        - surplusQuintals
        - harvestDate
        - location
        properties:
          kind:
            type: string
            const: CROP
          cropBlockId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          cropBlockClientId:
            type: string
            format: uuid
          cropLineClientId:
            type: string
            format: uuid
          productCode:
            type: string
          harvestType:
            $ref: '#/components/schemas/HarvestType'
          totalOutputQuintals:
            type: number
            format: double
            description: 'Unit: quintals.'
            minimum: 0
            maximum: 999.99
            multipleOf: 0.01
          surplusQuintals:
            type: number
            format: double
            description: 'Surplus for sale; <= total (`HARVEST_SURPLUS_EXCEEDS_TOTAL`). Unit: quintals.'
            minimum: 0
            maximum: 999.99
            multipleOf: 0.01
          harvestDate:
            type: string
            description: <= today and > the crop's previous harvest end date (`HARVEST_DATE_INVALID`).
            format: date
          earlyHarvestReason:
            type: string
            maxLength: 200
          interestedInMarketing:
            type: boolean
            description: Only for WHOLE_CROP with surplus > 0.
          photo:
            $ref: '#/components/schemas/Attachment'
          location:
            $ref: '#/components/schemas/GeoPoint'
    AtmHarvestItem:
      type: object
      required:
      - cropLineClientId
      - totalOutputQuintals
      - surplusQuintals
      - harvestDate
      properties:
        cropLineClientId:
          type: string
          format: uuid
        productCode:
          type: string
        totalOutputQuintals:
          type: number
          format: double
          description: 'Unit: quintals; > 0.'
          exclusiveMinimum: 0
          maximum: 999.99
          multipleOf: 0.01
        surplusQuintals:
          type: number
          format: double
          description: '<= total. Unit: quintals.'
          minimum: 0
          maximum: 999.99
          multipleOf: 0.01
        harvestDate:
          type: string
          description: Not in the future.
          format: date
    AtmHarvest:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: ATM harvest (append-only). WHOLE_CROP requires a photo and marks every ATM line COMPLETED.
        required:
        - kind
        - farmingModelClientId
        - harvestType
        - items
        - location
        properties:
          kind:
            type: string
            const: ATM
          farmingModelId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmingModelClientId:
            type: string
            format: uuid
          harvestType:
            $ref: '#/components/schemas/HarvestType'
          items:
            type: array
            items:
              $ref: '#/components/schemas/AtmHarvestItem'
            description: 'CONTINUOUS: >= 1 crop; WHOLE_CROP: every crop of the ATM.'
            minItems: 1
          photo:
            $ref: '#/components/schemas/Attachment'
          location:
            $ref: '#/components/schemas/GeoPoint'
    Harvest:
      oneOf:
      - $ref: '#/components/schemas/CropHarvest'
      - $ref: '#/components/schemas/AtmHarvest'
      discriminator:
        propertyName: kind
        mapping:
          CROP: '#/components/schemas/CropHarvest'
          ATM: '#/components/schemas/AtmHarvest'
    FarmingModelCropLine:
      type: object
      required:
      - clientId
      - productCode
      - lineType
      properties:
        clientId:
          type: string
          format: uuid
        productCode:
          type: string
        lineType:
          type: string
          enum:
          - VEGETABLE
          - PULSE
        harvestStatus:
          $ref: '#/components/schemas/HarvestStatus'
          readOnly: true
        harvestType:
          $ref: '#/components/schemas/HarvestType'
          readOnly: true
    AtmModel:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: ATM (Anytime Money) plot. C5 gets at most one (`ATM_C5_LIMIT_ONE`).
        required:
        - modelType
        - landAcres
        - raisedBeds
        - vegetableCrops
        - maizeCultivation
        - practiceCodes
        - incomeGeneration
        properties:
          modelType:
            type: string
            const: ATM
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmerClientId:
            type: string
            format: uuid
          landId:
            type:
            - integer
            - 'null'
            description: Server id (assigned by the App Service).
            format: int64
          landClientId:
            type:
            - string
            - 'null'
            description: Required unless the farmer is C5 (`ATM_LAND_REQUIRED`).
            format: uuid
          landChecksum:
            type: string
          tileId:
            type: string
          landAcres:
            type: number
            format: double
            description: 'ATM plot size. C5: <= `formLimits.atm.c5MaxAcres` (0.5, `ATM_C5_AREA_EXCEEDED`); others <= remaining
              land (`LAND_AREA_EXCEEDED`). Unit: acres.'
            minimum: 0.01
            maximum: 99.99
            multipleOf: 0.01
          raisedBeds:
            type: boolean
            description: 4 ft beds with 1 ft furrows.
          vegetableCrops:
            type: array
            items:
              $ref: '#/components/schemas/FarmingModelCropLine'
            minItems: 1
          relayCropping:
            type: boolean
          relayCroppingDetails:
            type: string
            description: Required when relayCropping is true.
            maxLength: 500
          maizeCultivation:
            type: boolean
          pulseCrops:
            type: array
            items:
              $ref: '#/components/schemas/FarmingModelCropLine'
          practiceCodes:
            type: array
            items:
              type: string
            minItems: 1
            uniqueItems: true
          incomeGeneration:
            type: boolean
          incomeGenerationDetails:
            type: number
            format: double
            description: Required when incomeGeneration is true.
            minimum: 0
          photo:
            $ref: '#/components/schemas/Attachment'
          geofence:
            $ref: '#/components/schemas/Geofence'
          harvestStatus:
            $ref: '#/components/schemas/HarvestStatus'
            readOnly: true
    DkgRecord:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: One answer per farmer per month; last write wins.
        required:
        - modelType
        - month
        - continuing
        properties:
          modelType:
            type: string
            const: DKG_365
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmerClientId:
            type: string
            format: uuid
          month:
            type: string
            description: Calendar month, `yyyy-MM`.
            pattern: ^\d{4}-(0[1-9]|1[0-2])$
          continuing:
            type: boolean
            description: Is the 365-day kitchen garden continuing this month?
    FarmingModel:
      oneOf:
      - $ref: '#/components/schemas/AtmModel'
      - $ref: '#/components/schemas/DkgRecord'
      discriminator:
        propertyName: modelType
        mapping:
          ATM: '#/components/schemas/AtmModel'
          DKG_365: '#/components/schemas/DkgRecord'
    KapSeasonValues:
      type: object
      description: KAP values for one season. Ceilings -> `KAP_VALUE_EXCEEDS_LIMIT`.
      properties:
        c1Acres:
          type: number
          format: double
          description: 'C1 extent; <= total land extent of the season. Unit: acres.'
          minimum: 0
          maximum: 99.99
          multipleOf: 0.01
        pmdsAcres:
          type: number
          format: double
          description: '<= c1Acres. Unit: acres.'
          minimum: 0
          maximum: 99.99
          multipleOf: 0.01
        s2sAcres:
          type: number
          format: double
          description: '<= c1Acres. Unit: acres.'
          minimum: 0
          maximum: 99.99
          multipleOf: 0.01
        aGradeAcres:
          type: number
          format: double
          description: '<= s2sAcres. Unit: acres.'
          minimum: 0
          maximum: 99.99
          multipleOf: 0.01
        s2sInCompactBlockAcres:
          type: number
          format: double
          description: '<= s2sAcres. Unit: acres.'
          minimum: 0
          maximum: 99.99
          multipleOf: 0.01
        desiCows:
          type: integer
          minimum: 0
          maximum: 999
        dkg:
          type: string
          enum:
          - 'NO'
          - SEASONAL
          - DKG_365
        atm:
          type: boolean
    KapPlan:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: Annual action plan (KAP). Resubmittable until the farmer, SHG or VO is endorsed (`KAP_LOCKED_ENDORSED`).
          C4 has no access (`CATEGORY_NOT_ALLOWED`); C4/C5 extents must be 0.
        required:
        - season
        - current
        properties:
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          farmerClientId:
            type: string
            format: uuid
          season:
            type: string
            description: Season label, e.g. `2025-26`.
            pattern: ^\d{4}-\d{2}$
          status:
            type: string
            enum:
            - NOT_STARTED
            - SUBMITTED
            - LOCKED
            readOnly: true
          lockedReason:
            type:
            - string
            - 'null'
            enum:
            - FARMER_ENDORSED
            - SHG_ENDORSED
            - VO_ENDORSED
            - null
            readOnly: true
          previousEndorsed:
            $ref: '#/components/schemas/KapSeasonValues'
            readOnly: true
          previousCurrent:
            $ref: '#/components/schemas/KapSeasonValues'
          current:
            $ref: '#/components/schemas/KapSeasonValues'
          pmdsPractisingYears:
            type: array
            items:
              type: string
              pattern: ^\d{4}-\d{2}$
            description: At least one year before the previous season (`KAP_PMDS_YEARS_REQUIRED`).
            uniqueItems: true
          dgc:
            type: object
            description: If practising, >= 1 season (`KAP_DGC_SEASON_REQUIRED`).
            required:
            - practising
            properties:
              practising:
                type: boolean
              seasons:
                type: array
                items:
                  type: string
                  enum:
                  - PMDS
                  - KHARIF
                  - RABI
                  - RDSS1
                  - RDSS2
                uniqueItems: true
          totalLandExtentPrevious:
            type: number
            format: double
            description: 'Unit: acres.'
            readOnly: true
          totalLandExtentCurrent:
            type: number
            format: double
            description: 'Unit: acres.'
            readOnly: true
          submittedAt:
            type: string
            format: date-time
            readOnly: true
    EndorsementStatus:
      type: string
      enum:
      - PENDING
      - ENDORSED
      - FLAGGED
      - FOR_REVIEW
    EndorsementEntityType:
      type: string
      enum:
      - VO
      - SHG
      - FARMER
    EndorsementActionType:
      type: string
      enum:
      - ENDORSE
      - FLAG
      - NOT_RESOLVED
      - MARK_RESOLVED
      - UPLOAD_ABSTRACT
    Endorsement:
      type: object
      required:
      - entityType
      - entityId
      - status
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
          readOnly: true
        entityType:
          $ref: '#/components/schemas/EndorsementEntityType'
        entityId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        entityCode:
          type: string
        name:
          type: string
        status:
          $ref: '#/components/schemas/EndorsementStatus'
        vo:
          $ref: '#/components/schemas/GeoRef'
        shg:
          $ref: '#/components/schemas/GeoRef'
        endorsedBy:
          $ref: '#/components/schemas/UserRef'
        endorsedAt:
          type: string
          format: date-time
        reviewedBy:
          $ref: '#/components/schemas/UserRef'
        reviewedAt:
          type: string
          format: date-time
        abstract:
          $ref: '#/components/schemas/Attachment'
        allowedActions:
          type: array
          items:
            $ref: '#/components/schemas/EndorsementActionType'
          description: Transitions the caller may perform now.
          readOnly: true
        commentCount:
          type: integer
          minimum: 0
          readOnly: true
        version:
          type: integer
          format: int64
          readOnly: true
    EndorsementPage:
      type: object
      required:
      - items
      - page
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Endorsement'
        page:
          $ref: '#/components/schemas/PageInfo'
    EndorsementAction:
      type: object
      description: Endorsement state-machine transition. PENDING -> ENDORSE|FLAG; FLAGGED/FOR_REVIEW -> ENDORSE|NOT_RESOLVED
        (Unit); FLAGGED -> MARK_RESOLVED (Cadre, becomes FOR_REVIEW); ENDORSED -> UPLOAD_ABSTRACT only. VO/SHG status cascades
        to its farmers and locks their KAP.
      required:
      - clientId
      - entityType
      - entityId
      - action
      properties:
        clientId:
          type: string
          format: uuid
        entityType:
          $ref: '#/components/schemas/EndorsementEntityType'
        entityId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        action:
          $ref: '#/components/schemas/EndorsementActionType'
        comment:
          type: string
          maxLength: 1000
        abstractFileId:
          type: string
          description: For UPLOAD_ABSTRACT (image, PDF or Word).
          format: uuid
        farmerIds:
          type: array
          items:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          description: 'Bulk endorse: farmers under a VO''s non-SHG list. The server endorses those with KAP submitted and
            rejects the rest per item.'
          maxItems: 500
        resultingStatus:
          $ref: '#/components/schemas/EndorsementStatus'
          readOnly: true
    EndorsementComment:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        required:
        - entityType
        - entityId
        - text
        properties:
          entityType:
            $ref: '#/components/schemas/EndorsementEntityType'
          entityId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          text:
            type: string
            minLength: 1
            maxLength: 1000
            description: Empty comments are rejected (`COMMENT_EMPTY`).
          commentType:
            type: string
            enum:
            - USER
            - SYSTEM
            readOnly: true
          postedBy:
            $ref: '#/components/schemas/UserRef'
            readOnly: true
          postedAt:
            type: string
            format: date-time
            readOnly: true
    EndorsementCommentPage:
      type: object
      required:
      - items
      - page
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/EndorsementComment'
        page:
          $ref: '#/components/schemas/PageInfo'
    EligibilityBlocker:
      type: object
      required:
      - code
      - message
      properties:
        code:
          $ref: '#/components/schemas/ProblemCode'
        message:
          type: string
        farmers:
          type: array
          items:
            $ref: '#/components/schemas/FarmerRef'
        shgs:
          type: array
          items:
            $ref: '#/components/schemas/GeoRef'
    EndorsementEligibility:
      type: object
      required:
      - entityType
      - entityId
      - action
      - eligible
      - blockers
      properties:
        entityType:
          $ref: '#/components/schemas/EndorsementEntityType'
        entityId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        action:
          $ref: '#/components/schemas/EndorsementActionType'
        eligible:
          type: boolean
        blockers:
          type: array
          items:
            $ref: '#/components/schemas/EligibilityBlocker'
        evaluatedAt:
          type: string
          format: date-time
    AbstractFile:
      type: object
      required:
      - url
      - mimeType
      - expiresAt
      properties:
        url:
          type: string
          format: uri
        fileName:
          type: string
        mimeType:
          type: string
        expiresAt:
          type: string
          format: date-time
    CompactBlockMember:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: A crop tile in a compact block. Deleting a member requires a reason.
        properties:
          compactBlockId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          compactBlockClientId:
            type: string
            format: uuid
          cropBlockId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          cropBlockClientId:
            type: string
            format: uuid
          tileId:
            type: string
          farmerId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          landId:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
          shg:
            $ref: '#/components/schemas/GeoRef'
          vo:
            $ref: '#/components/schemas/GeoRef'
          acres:
            type: number
            format: double
            description: 'Crop acres of the tile. Unit: acres.'
            readOnly: true
          joinedAt:
            type: string
            format: date-time
            readOnly: true
          leftAt:
            type: string
            format: date-time
            readOnly: true
          inactiveReason:
            type: string
    CompactBlock:
      allOf:
      - $ref: '#/components/schemas/EntityMeta'
      - type: object
        description: 'Group of contiguous S2S tiles from several farmers, >= 5 acres. Server-authoritative membership: a tile
          in another active block -> `TILE_IN_OTHER_BLOCK`.'
        required:
        - name
        properties:
          name:
            type: string
            description: Unique among the user's active blocks (`COMPACT_BLOCK_NAME_TAKEN`).
            minLength: 1
            maxLength: 100
          tileId:
            type: string
          voIds:
            type: array
            items:
              type: integer
              description: Server id (assigned by the App Service).
              format: int64
          members:
            type: array
            items:
              $ref: '#/components/schemas/CompactBlockMember'
            minItems: 1
          totalAcres:
            type: number
            format: double
            description: 'Sum of crop acres of every member tile; >= `formLimits.compactBlock.minTotalAcres` (`COMPACT_BLOCK_BELOW_5_ACRES`).
              Unit: acres.'
            readOnly: true
          flags:
            type: array
            items:
              type: string
              enum:
              - BELOW_5_ACRES
              - INACTIVE_FARMER
              - NOT_FARMER
              - NOT_S2S
            readOnly: true
          inactiveReason:
            type: string
            description: Required on delete.
          note:
            type: string
            maxLength: 500
    CompactBlockPage:
      type: object
      required:
      - items
      - page
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CompactBlock'
        page:
          $ref: '#/components/schemas/PageInfo'
    EligibleTile:
      type: object
      required:
      - cropBlockId
      - tileId
      - farmer
      - acres
      - hasGeofence
      properties:
        cropBlockId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        cropBlockClientId:
          type: string
          format: uuid
        tileId:
          type: string
        farmer:
          $ref: '#/components/schemas/FarmerRef'
        landId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        surveyNo:
          type: string
        khataNo:
          type: string
        acres:
          type: number
          format: double
          description: 'Crop acres. Unit: acres.'
        shg:
          $ref: '#/components/schemas/GeoRef'
        vo:
          $ref: '#/components/schemas/GeoRef'
        hasGeofence:
          type: boolean
        isS2S:
          type: boolean
        cropCodes:
          type: array
          items:
            type: string
    EligibleTilesResponse:
      type: object
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/EligibleTile'
        season:
          type: string
          description: Season label, e.g. `2025-26`.
          pattern: ^\d{4}-\d{2}$
    AssessmentType:
      type: string
      enum:
      - CERTIFICATION
      - ACTIVITY_TRACKER
      - UNIT_INCHARGE_VO
      - SHG
      - UNIT_INCHARGE
      - MONTHLY_PROGRESS
    AssessmentQuestion:
      type: object
      required:
      - code
      - text
      - type
      - required
      properties:
        code:
          type: string
        text:
          $ref: '#/components/schemas/LocalizedText'
        type:
          type: string
          enum:
          - SUBJECTIVE
          - NUMBER
          - DATE
          - SINGLE_CHOICE
          - MULTI_CHOICE
          - SCALE
          - FILE
          - SIGNATURE
          - CROP_QUANTITY
          - PICKER_VO
          - PICKER_SHG
          - PICKER_LEAD_FARMER
          - PICKER_CROP
          - TWO_NUMBERS
        required:
          type: boolean
        options:
          type: array
          items:
            $ref: '#/components/schemas/RefOption'
        minSelections:
          type: integer
          minimum: 0
        maxSelections:
          type: integer
          minimum: 1
        hints:
          type: array
          items:
            $ref: '#/components/schemas/LocalizedText'
        max:
          type: number
          format: double
    Assessment:
      type: object
      required:
      - id
      - code
      - type
      - questions
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        code:
          type: string
          examples:
          - APCNF_SURVEY_CERTIFICATION
        type:
          $ref: '#/components/schemas/AssessmentType'
        title:
          $ref: '#/components/schemas/LocalizedText'
        assesseeType:
          type: string
          enum:
          - FARMER
          - VO
          - SHG
          - USER
        questions:
          type: array
          items:
            $ref: '#/components/schemas/AssessmentQuestion'
        version:
          type: integer
          format: int64
    AssessmentSubmission:
      type: object
      required:
      - id
      - assessmentId
      - responseDate
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        clientId:
          type: string
          format: uuid
        assessmentId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        assesseeId:
          type: string
        responseDate:
          type: string
          format: date
        submittedAt:
          type: string
          format: date-time
    AssessmentPage:
      type: object
      required:
      - assessments
      - submissions
      - page
      properties:
        assessments:
          type: array
          items:
            $ref: '#/components/schemas/Assessment'
        submissions:
          type: array
          items:
            $ref: '#/components/schemas/AssessmentSubmission'
        page:
          $ref: '#/components/schemas/PageInfo'
    AssessmentAnswer:
      type: object
      required:
      - questionCode
      properties:
        questionCode:
          type: string
        optionCodes:
          type: array
          items:
            type: string
        text:
          type: string
          maxLength: 2000
        number:
          type: number
          format: double
        numbers:
          type: array
          items:
            type: number
            format: double
          maxItems: 2
        date:
          type: string
          format: date
        fileIds:
          type: array
          items:
            type: string
            format: uuid
        crops:
          type: array
          items:
            type: object
            required:
            - productCode
            - quantity
            properties:
              productCode:
                type: string
              quantity:
                type: number
                format: double
                description: 'Unit: quintals per hectare.'
                exclusiveMinimum: 0
        refIds:
          type: array
          items:
            type: string
          description: Selected VO / SHG / lead-farmer ids for picker questions.
    AssessmentResponse:
      type: object
      description: 'Assessment submission (append-only). All required questions answered (`ASSESSMENT_INCOMPLETE`). Activity
        tracker: submit today only at/after 18:00 local (`ACTIVITY_SUBMIT_TOO_EARLY`), one per day (`ACTIVITY_ALREADY_SUBMITTED`).'
      required:
      - clientId
      - assessmentId
      - assessmentType
      - assessee
      - responseDate
      - answers
      properties:
        clientId:
          type: string
          format: uuid
        assessmentId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        assessmentType:
          $ref: '#/components/schemas/AssessmentType'
        assessee:
          type: object
          required:
          - type
          properties:
            type:
              type: string
              enum:
              - FARMER
              - VO
              - SHG
              - USER
            id:
              type: string
            clientId:
              type: string
              format: uuid
        responseDate:
          type: string
          description: Not in the future (`FUTURE_DATE`).
          format: date
        answers:
          type: array
          items:
            $ref: '#/components/schemas/AssessmentAnswer'
          minItems: 1
        signatureFileId:
          type: string
          description: Required for CERTIFICATION self-declaration (`SIGNATURE_REQUIRED`).
          format: uuid
        photoFileIds:
          type: array
          items:
            type: string
            format: uuid
        location:
          $ref: '#/components/schemas/GeoPoint'
    AnalyticsCounts:
      type: object
      properties:
        farmersSurveyed:
          type: integer
          minimum: 0
        c1Farmers:
          type: integer
          minimum: 0
        c1Acres:
          type: number
          format: double
          minimum: 0
        s2sFarmers:
          type: integer
          minimum: 0
        s2sAcres:
          type: number
          format: double
          minimum: 0
        pmdsFarmers:
          type: integer
          minimum: 0
        atmFarmers:
          type: integer
          minimum: 0
        aGradeFarmers:
          type: integer
          minimum: 0
        target:
          type: integer
          minimum: 0
        coveragePercent:
          type: number
          format: double
          minimum: 0
          maximum: 100
    AnalyticsBreakdownRow:
      type: object
      required:
      - level
      - id
      - counts
      properties:
        level:
          type: string
          enum:
          - CADRE
          - VO
          - SHG
        id:
          type: string
        name:
          type: string
        counts:
          $ref: '#/components/schemas/AnalyticsCounts'
    PrincipleAdoption:
      type: object
      required:
      - code
      - farmers
      properties:
        code:
          type: string
        label:
          $ref: '#/components/schemas/LocalizedText'
        farmers:
          type: integer
          minimum: 0
        percent:
          type: number
          format: double
          minimum: 0
          maximum: 100
    AnalyticsDrilldown:
      type: object
      required:
      - metric
      - period
      - farmerIds
      properties:
        metric:
          type: string
        period:
          type: string
          enum:
          - YESTERDAY
          - MONTH
          - OVERALL
        farmerIds:
          type: array
          items:
            type: integer
            description: Server id (assigned by the App Service).
            format: int64
    AnalyticsDashboard:
      type: object
      required:
      - scope
      - asOf
      - yesterday
      - month
      - overall
      properties:
        scope:
          type: string
          enum:
          - cadre
          - unit
        asOf:
          type: string
          format: date-time
        yesterday:
          $ref: '#/components/schemas/AnalyticsCounts'
        month:
          $ref: '#/components/schemas/AnalyticsCounts'
        overall:
          $ref: '#/components/schemas/AnalyticsCounts'
        ninePrinciples:
          type: array
          items:
            $ref: '#/components/schemas/PrincipleAdoption'
        breakdown:
          type: array
          items:
            $ref: '#/components/schemas/AnalyticsBreakdownRow'
        drilldowns:
          type: array
          items:
            $ref: '#/components/schemas/AnalyticsDrilldown'
        filterOptions:
          type: object
          properties:
            cadres:
              type: array
              items:
                $ref: '#/components/schemas/UserRef'
            vos:
              type: array
              items:
                $ref: '#/components/schemas/GeoRef'
            shgs:
              type: array
              items:
                $ref: '#/components/schemas/GeoRef'
    Notification:
      type: object
      required:
      - id
      - category
      - kind
      - title
      - createdAt
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        category:
          type: string
          enum:
          - MIS
          - SYS
        kind:
          type: string
          enum:
          - ENDORSEMENT_STATUS
          - ENDORSEMENT_COMMENT
          - BROADCAST
          - REMOTE_ACTION
        title:
          $ref: '#/components/schemas/LocalizedText'
        body:
          $ref: '#/components/schemas/LocalizedText'
        farmerId:
          type:
          - integer
          - 'null'
          description: Server id (assigned by the App Service).
          format: int64
        entityType:
          $ref: '#/components/schemas/EndorsementEntityType'
        entityId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        createdAt:
          type: string
          format: date-time
    NotificationPage:
      type: object
      required:
      - items
      - page
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Notification'
        page:
          $ref: '#/components/schemas/PageInfo'
        serverTime:
          type: string
          format: date-time
    Device:
      type: object
      required:
      - deviceId
      properties:
        deviceId:
          type: string
          format: uuid
        brand:
          type: string
        model:
          type: string
        osVersion:
          type: string
        appVersion:
          type: string
        status:
          type: string
          enum:
          - LIVE
          - EXPIRED
          - BLOCKED
          readOnly: true
        boundAt:
          type: string
          format: date-time
          readOnly: true
        lastSeenAt:
          type: string
          format: date-time
          readOnly: true
    DeviceRegistration:
      type: object
      required:
      - brand
      - model
      - osVersion
      - appVersion
      properties:
        brand:
          type: string
          maxLength: 50
        model:
          type: string
          maxLength: 50
        osVersion:
          type: string
          maxLength: 20
        appVersion:
          type: string
          maxLength: 20
        pushToken:
          type: string
          description: FCM registration token.
          maxLength: 4096
        locale:
          type: string
          enum:
          - en
          - te
          - ta
    PushTokenRequest:
      type: object
      required:
      - pushToken
      properties:
        pushToken:
          type: string
          minLength: 10
          maxLength: 4096
        provider:
          type: string
          enum:
          - FCM
          default: FCM
    RemoteAction:
      type: object
      required:
      - id
      - type
      - createdAt
      properties:
        id:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        type:
          type: string
          enum:
          - LOGOUT
          - CLEAR_APP_DATA
          - FORCE_SYNC
          - MESSAGE
        message:
          $ref: '#/components/schemas/LocalizedText'
        createdAt:
          type: string
          format: date-time
        drainOutboxFirst:
          type: boolean
          description: Try to sync pending ops before executing.
    RemoteActionList:
      type: object
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/RemoteAction'
    ActionCompletion:
      type: object
      required:
      - status
      - executedAt
      properties:
        status:
          type: string
          enum:
          - COMPLETED
          - FAILED
        executedAt:
          type: string
          format: date-time
        diagnosticsFileId:
          type: string
          description: Optional encrypted, redacted diagnostics bundle (`POST /files`, kind `DIAGNOSTICS`). Never a raw database.
          format: uuid
        pendingOpsAtExecution:
          type: integer
          minimum: 0
        note:
          type: string
          maxLength: 500
    BindingTakeoverRequest:
      type: object
      required:
      - confirm
      properties:
        confirm:
          type: boolean
          const: true
        reason:
          type: string
          const: BINDING_REMOVED
    SyncEntityType:
      type: string
      enum:
      - farmer
      - land
      - livestock
      - crop_stock
      - crop_block
      - crop_harvest
      - farming_model
      - atm_harvest
      - kap_plan
      - compact_block
      - compact_block_member
      - endorsement_action
      - endorsement_comment
      - assessment_response
    SyncOpKind:
      type: string
      enum:
      - create
      - update
      - delete
    SyncRefs:
      type: object
      description: Parent references by client UUID. The server resolves them through its client-to-server id map, including
        parents created earlier in the same batch.
      properties:
        farmerClientId:
          type: string
          format: uuid
        landClientId:
          type: string
          format: uuid
        cropBlockClientId:
          type: string
          format: uuid
        farmingModelClientId:
          type: string
          format: uuid
        compactBlockClientId:
          type: string
          format: uuid
    SyncOpBase:
      type: object
      required:
      - opId
      - entityType
      - op
      - clientId
      properties:
        opId:
          type: string
          description: Idempotency key of this op (the outbox row id). Retained >= 30 days.
          format: uuid
        entityType:
          $ref: '#/components/schemas/SyncEntityType'
        op:
          $ref: '#/components/schemas/SyncOpKind'
        clientId:
          type: string
          description: Client UUID of the entity the op touches.
          format: uuid
        serverId:
          type: integer
          description: Known server id (optional; the server can resolve from `clientId`).
          format: int64
        baseVersion:
          type:
          - integer
          - 'null'
          description: Server version the edit is based on; null for creates.
          format: int64
        refs:
          $ref: '#/components/schemas/SyncRefs'
        dependsOn:
          type: array
          items:
            type: string
            format: uuid
          description: opIds that must be applied first (e.g. a file-referencing op after its parent).
        reason:
          type: string
          description: Required for delete ops (spaces are allowed; the server normalises).
          maxLength: 200
        clientTime:
          type: string
          description: When the user made the change on the device.
          format: date-time
    SyncOp_Farmer:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`farmer` op. Payload: full `Farmer` for create, changed fields only for update, omitted for delete.'
        properties:
          entityType:
            type: string
            const: farmer
          payload:
            $ref: '#/components/schemas/Farmer'
    SyncOp_Land:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`land` op. Payload: full `Land` for create, changed fields only for update, omitted for delete.'
        properties:
          entityType:
            type: string
            const: land
          payload:
            $ref: '#/components/schemas/Land'
    SyncOp_Livestock:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`livestock` op. Payload: full `Livestock` for create, changed fields only for update, omitted for delete.'
        properties:
          entityType:
            type: string
            const: livestock
          payload:
            $ref: '#/components/schemas/Livestock'
    SyncOp_CropStock:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`crop_stock` op. Payload: full `CropStock` for create, changed fields only for update, omitted for delete.'
        properties:
          entityType:
            type: string
            const: crop_stock
          payload:
            $ref: '#/components/schemas/CropStock'
    SyncOp_CropBlock:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`crop_block` op. Payload: full `CropBlock` for create, changed fields only for update, omitted for delete.'
        properties:
          entityType:
            type: string
            const: crop_block
          payload:
            $ref: '#/components/schemas/CropBlock'
    SyncOp_CropHarvest:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`crop_harvest` op. Payload: full `CropHarvest` for create, changed fields only for update, omitted for
          delete.'
        properties:
          entityType:
            type: string
            const: crop_harvest
          payload:
            $ref: '#/components/schemas/CropHarvest'
    SyncOp_FarmingModel:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`farming_model` op. Payload: full `FarmingModel` for create, changed fields only for update, omitted
          for delete.'
        properties:
          entityType:
            type: string
            const: farming_model
          payload:
            $ref: '#/components/schemas/FarmingModel'
    SyncOp_AtmHarvest:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`atm_harvest` op. Payload: full `AtmHarvest` for create, changed fields only for update, omitted for
          delete.'
        properties:
          entityType:
            type: string
            const: atm_harvest
          payload:
            $ref: '#/components/schemas/AtmHarvest'
    SyncOp_KapPlan:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`kap_plan` op. Payload: full `KapPlan` for create, changed fields only for update, omitted for delete.'
        properties:
          entityType:
            type: string
            const: kap_plan
          payload:
            $ref: '#/components/schemas/KapPlan'
    SyncOp_CompactBlock:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`compact_block` op. Payload: full `CompactBlock` for create, changed fields only for update, omitted
          for delete.'
        properties:
          entityType:
            type: string
            const: compact_block
          payload:
            $ref: '#/components/schemas/CompactBlock'
    SyncOp_CompactBlockMember:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`compact_block_member` op. Payload: full `CompactBlockMember` for create, changed fields only for update,
          omitted for delete.'
        properties:
          entityType:
            type: string
            const: compact_block_member
          payload:
            $ref: '#/components/schemas/CompactBlockMember'
    SyncOp_EndorsementAction:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`endorsement_action` op. Payload: full `EndorsementAction` for create, changed fields only for update,
          omitted for delete.'
        properties:
          entityType:
            type: string
            const: endorsement_action
          payload:
            $ref: '#/components/schemas/EndorsementAction'
    SyncOp_EndorsementComment:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`endorsement_comment` op. Payload: full `EndorsementComment` for create, changed fields only for update,
          omitted for delete.'
        properties:
          entityType:
            type: string
            const: endorsement_comment
          payload:
            $ref: '#/components/schemas/EndorsementComment'
    SyncOp_AssessmentResponse:
      allOf:
      - $ref: '#/components/schemas/SyncOpBase'
      - type: object
        description: '`assessment_response` op. Payload: full `AssessmentResponse` for create, changed fields only for update,
          omitted for delete.'
        properties:
          entityType:
            type: string
            const: assessment_response
          payload:
            $ref: '#/components/schemas/AssessmentResponse'
    SyncOp:
      oneOf:
      - $ref: '#/components/schemas/SyncOp_Farmer'
      - $ref: '#/components/schemas/SyncOp_Land'
      - $ref: '#/components/schemas/SyncOp_Livestock'
      - $ref: '#/components/schemas/SyncOp_CropStock'
      - $ref: '#/components/schemas/SyncOp_CropBlock'
      - $ref: '#/components/schemas/SyncOp_CropHarvest'
      - $ref: '#/components/schemas/SyncOp_FarmingModel'
      - $ref: '#/components/schemas/SyncOp_AtmHarvest'
      - $ref: '#/components/schemas/SyncOp_KapPlan'
      - $ref: '#/components/schemas/SyncOp_CompactBlock'
      - $ref: '#/components/schemas/SyncOp_CompactBlockMember'
      - $ref: '#/components/schemas/SyncOp_EndorsementAction'
      - $ref: '#/components/schemas/SyncOp_EndorsementComment'
      - $ref: '#/components/schemas/SyncOp_AssessmentResponse'
      discriminator:
        propertyName: entityType
        mapping:
          farmer: '#/components/schemas/SyncOp_Farmer'
          land: '#/components/schemas/SyncOp_Land'
          livestock: '#/components/schemas/SyncOp_Livestock'
          crop_stock: '#/components/schemas/SyncOp_CropStock'
          crop_block: '#/components/schemas/SyncOp_CropBlock'
          crop_harvest: '#/components/schemas/SyncOp_CropHarvest'
          farming_model: '#/components/schemas/SyncOp_FarmingModel'
          atm_harvest: '#/components/schemas/SyncOp_AtmHarvest'
          kap_plan: '#/components/schemas/SyncOp_KapPlan'
          compact_block: '#/components/schemas/SyncOp_CompactBlock'
          compact_block_member: '#/components/schemas/SyncOp_CompactBlockMember'
          endorsement_action: '#/components/schemas/SyncOp_EndorsementAction'
          endorsement_comment: '#/components/schemas/SyncOp_EndorsementComment'
          assessment_response: '#/components/schemas/SyncOp_AssessmentResponse'
      description: One mutation. `payload` is oneOf per entity, selected by `entityType`.
    SyncBatchRequest:
      type: object
      required:
      - batchId
      - ops
      properties:
        batchId:
          type: string
          description: Client-generated; resending the same batch is safe.
          format: uuid
        clientTime:
          type: string
          format: date-time
        ops:
          type: array
          items:
            $ref: '#/components/schemas/SyncOp'
          description: Processed in order. Max 100 (`BATCH_TOO_LARGE`).
          minItems: 1
          maxItems: 100
    SyncOpStatus:
      type: string
      description: applied = done; duplicate = idempotency key seen, original result returned; conflict = stale baseVersion,
        serverState attached; rejected = validation failed, error attached; blocked = a dependency in this batch failed; retry
        = transient, resend later.
      enum:
      - applied
      - duplicate
      - rejected
      - conflict
      - blocked
      - retry
    SyncEntity:
      description: Current server state of the entity; schema matches `entityType`.
      anyOf:
      - $ref: '#/components/schemas/Farmer'
      - $ref: '#/components/schemas/Land'
      - $ref: '#/components/schemas/Livestock'
      - $ref: '#/components/schemas/CropStock'
      - $ref: '#/components/schemas/CropBlock'
      - $ref: '#/components/schemas/CropHarvest'
      - $ref: '#/components/schemas/FarmingModel'
      - $ref: '#/components/schemas/AtmHarvest'
      - $ref: '#/components/schemas/KapPlan'
      - $ref: '#/components/schemas/CompactBlock'
      - $ref: '#/components/schemas/CompactBlockMember'
      - $ref: '#/components/schemas/EndorsementAction'
      - $ref: '#/components/schemas/EndorsementComment'
      - $ref: '#/components/schemas/AssessmentResponse'
    SyncOpResult:
      type: object
      required:
      - opId
      - clientId
      - entityType
      - status
      properties:
        opId:
          type: string
          format: uuid
        clientId:
          type: string
          format: uuid
        entityType:
          $ref: '#/components/schemas/SyncEntityType'
        status:
          $ref: '#/components/schemas/SyncOpStatus'
        serverId:
          type:
          - integer
          - 'null'
          description: Server id (assigned by the App Service).
          format: int64
        serverVersion:
          type:
          - integer
          - 'null'
          format: int64
        error:
          description: Present for rejected / conflict / blocked / retry.
          anyOf:
          - $ref: '#/components/schemas/Problem'
          - type: 'null'
        serverState:
          description: Present for conflict (and rejected PART_OF_COMPACT_BLOCK / LAND_CHECKSUM_MISMATCH).
          anyOf:
          - $ref: '#/components/schemas/SyncEntity'
          - type: 'null'
        blockedBy:
          type: string
          description: 'For blocked: the failing opId.'
          format: uuid
    IdMapping:
      type: object
      required:
      - entityType
      - clientId
      - serverId
      properties:
        entityType:
          $ref: '#/components/schemas/SyncEntityType'
        clientId:
          type: string
          format: uuid
        serverId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
    SyncBatchResult:
      type: object
      required:
      - batchId
      - results
      - idMap
      properties:
        batchId:
          type: string
          format: uuid
        processedAt:
          type: string
          format: date-time
        results:
          type: array
          items:
            $ref: '#/components/schemas/SyncOpResult'
          description: One per op, same order.
        idMap:
          type: array
          items:
            $ref: '#/components/schemas/IdMapping'
          description: Client UUID -> server id for every entity created or referenced.
        changesCursor:
          type: string
          description: Cursor after this batch's own writes; pass to `/farmers/changes` to skip echoes.
    SyncBatchStatus:
      type: object
      required:
      - batchId
      - state
      properties:
        batchId:
          type: string
          format: uuid
        state:
          type: string
          enum:
          - QUEUED
          - PROCESSING
          - COMPLETED
          - FAILED
        receivedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
        progress:
          type: object
          properties:
            total:
              type: integer
              minimum: 0
            processed:
              type: integer
              minimum: 0
        result:
          anyOf:
          - $ref: '#/components/schemas/SyncBatchResult'
          - type: 'null'
    SnapshotPage:
      type: object
      required:
      - scope
      - items
      - page
      properties:
        scope:
          type: string
          description: Work node the snapshot covers, e.g. `vo:4101`.
        items:
          type: array
          items:
            $ref: '#/components/schemas/FarmerAggregate'
        page:
          $ref: '#/components/schemas/PageInfo'
        totalCount:
          type: integer
          minimum: 0
        deltaCursor:
          type:
          - string
          - 'null'
          description: 'Only on the last page: start cursor for `/farmers/changes`.'
        generatedAt:
          type: string
          format: date-time
    ChangeUpsert:
      type: object
      required:
      - entityType
      - serverId
      - version
      - entity
      properties:
        entityType:
          $ref: '#/components/schemas/SyncEntityType'
        serverId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        clientId:
          type: string
          format: uuid
        farmerId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        version:
          type: integer
          format: int64
        updatedAt:
          type: string
          format: date-time
        entity:
          $ref: '#/components/schemas/SyncEntity'
    Tombstone:
      type: object
      required:
      - entityType
      - serverId
      - deletedAt
      properties:
        entityType:
          $ref: '#/components/schemas/SyncEntityType'
        serverId:
          type: integer
          description: Server id (assigned by the App Service).
          format: int64
        clientId:
          type: string
          format: uuid
        deletedAt:
          type: string
          format: date-time
        reason:
          type: string
          enum:
          - DELETED
          - DISABLED
          - OUT_OF_SCOPE
          - MERGED
    ChangesPage:
      type: object
      required:
      - upserts
      - tombstones
      - page
      - cursor
      properties:
        upserts:
          type: array
          items:
            $ref: '#/components/schemas/ChangeUpsert'
        tombstones:
          type: array
          items:
            $ref: '#/components/schemas/Tombstone'
        page:
          $ref: '#/components/schemas/PageInfo'
        cursor:
          type: string
          description: Store and resend; valid for at least 30 days (`CURSOR_EXPIRED` / 410 -> re-snapshot).
    FileKind:
      type: string
      enum:
      - PHOTO_FARMER
      - PHOTO_CROP
      - PHOTO_ATM
      - PHOTO_HARVEST
      - PHOTO_ACTIVITY
      - KML_GEOFENCE
      - SIGNATURE
      - ENDORSEMENT_ABSTRACT
      - ASSESSMENT_ATTACHMENT
      - DIAGNOSTICS
    FileUploadRequest:
      type: object
      required:
      - fileName
      - mimeType
      - sizeBytes
      - sha256
      - kind
      - clientId
      properties:
        fileName:
          type: string
          maxLength: 200
        mimeType:
          type: string
          examples:
          - image/jpeg
        sizeBytes:
          type: integer
          minimum: 1
          maximum: 20971520
          description: Max 20 MB (`FILE_TOO_LARGE`).
        sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        kind:
          $ref: '#/components/schemas/FileKind'
        clientId:
          type: string
          description: Client UUID of the file (idempotent create).
          format: uuid
        ownerClientId:
          type: string
          description: 'Optional: entity the file will belong to (it may not exist on the server yet).'
          format: uuid
    FileUploadForm:
      type: object
      description: Single-shot multipart upload for small files (<= 1 MB).
      required:
      - file
      - metadata
      properties:
        file:
          type: string
          contentMediaType: application/octet-stream
          description: File bytes.
        metadata:
          $ref: '#/components/schemas/FileUploadRequest'
    FileUpload:
      type: object
      required:
      - fileId
      - status
      - uploadOffset
      - sizeBytes
      properties:
        fileId:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - PENDING
          - COMPLETE
        uploadOffset:
          type: integer
          minimum: 0
        sizeBytes:
          type: integer
          minimum: 1
        uploadUrl:
          type: string
          description: Where to PATCH chunks (this API) or a presigned URL.
          format: uri
        expiresAt:
          type: string
          description: Incomplete uploads expire after this.
          format: date-time
    FileInfo:
      type: object
      required:
      - fileId
      - kind
      - status
      properties:
        fileId:
          type: string
          format: uuid
        kind:
          $ref: '#/components/schemas/FileKind'
        mimeType:
          type: string
        sizeBytes:
          type: integer
          minimum: 1
        sha256:
          type: string
        status:
          type: string
          enum:
          - PENDING
          - COMPLETE
        url:
          type: string
          description: Signed download URL (short-lived).
          format: uri
        urlExpiresAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
    TelemetryEvent:
      type: object
      description: 'No PII: no free text, names, phone numbers, Aadhaar or request bodies. Unknown properties are rejected.'
      required:
      - type
      - at
      properties:
        type:
          type: string
          enum:
          - screen_view
          - action
          - sync_health
          - error
        at:
          type: string
          format: date-time
        screen:
          type: string
          pattern: ^[a-z0-9_]{1,64}$
        action:
          type: string
          pattern: ^[a-z0-9_]{1,64}$
        errorCode:
          $ref: '#/components/schemas/ProblemCode'
        durationMs:
          type: integer
          minimum: 0
        counters:
          type: object
          description: Named integer counters, e.g. `pendingOps`, `failedOps`, `oldestPendingAgeMinutes`.
          properties: {}
          additionalProperties:
            type: integer
            minimum: 0
          propertyNames:
            pattern: ^[a-zA-Z0-9_]{1,64}$
      additionalProperties: false
    TelemetryBatch:
      type: object
      required:
      - events
      properties:
        sessionId:
          type: string
          description: Random per app session; not linked to the user.
          format: uuid
        events:
          type: array
          items:
            $ref: '#/components/schemas/TelemetryEvent'
          minItems: 1
          maxItems: 500
      additionalProperties: false
    TelemetryAck:
      type: object
      required:
      - accepted
      - dropped
      properties:
        accepted:
          type: integer
          minimum: 0
        dropped:
          type: integer
          minimum: 0
