API contracts / App Service v2 / Farmers

Delta sync: upserts and tombstones since a cursor

OperationgetFarmerChanges
Call
GET /gateway/urvi-app-service/api/v2/farmers/changes
Group
Farmers
Features
Home and bottom bar, Farmer list & search, Farmer hub (option tiles), Land assets, Crop Actual (portfolio), ATM model (Anytime Money plot), Compact Block, KAP / Annual Action Plan 2025-26, Settings, Pending sync
Replaces today

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

Parameters

NameInTypeRequiredMeaningExample
X-App-VersionheaderstringYesApp version `major.minor.patch+build`. Too old -> 426 `VERSION_TOO_OLD`. · pattern ^\d+\.\d+\.\d+(\+\d+)?$2.0.0+200
X-Device-Idheaderstring (uuid)YesApp-scoped device UUID (not ANDROID_ID).6b1f2c3d-0000-4000-8000-00000000d001
X-Request-Idheaderstring (uuid)NoClient-generated request id for tracing; echoed in responses and problems.0e0c5a1b-0000-4000-8000-0000000000a1
Accept-LanguageheaderstringNoLanguage for messages and labels. · one of en, te, ta; default "en"te
X-TenantheaderstringNoTenant code; must match the token. Urvi: `ap.gov`. · one of ap.govap.gov
cursorquerystringYesCursor from the snapshot's `deltaCursor` or the previous changes page. · max length 512chg_eyJ2IjoxMjM0NX0
scopequerystringNoSame scope as the snapshot; defaults to all the user's scopes. · pattern ^(vo:\d+|unit|self)$vo:4101
pageSizequeryintegerNoItems per page. · ≥ 1; ≤ 500; default 100100

Request

No body.

Responses

200 Changes page.

Schema ChangesPage.

Example 200 body
{
  "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"
}
FieldTypeRequiredRulesMeaning
upsertsChangeUpsert[]Yes
tombstonesTombstone[]Yes
pagePageInfoYes
cursorstringYesStore and resend; valid for at least 30 days (`CURSOR_EXPIRED` / 410 -> re-snapshot).

400 Malformed request or schema violation.

Schema Problem.

Example 400 body
{
  "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."
    }
  ]
}

401 Missing, invalid or expired access token. Refresh once, then ask the user to log in; pending outbox ops stay intact.

Schema Problem.

Example 401 body
{
  "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"
}

403 Authenticated but not allowed (role, tenant, device binding or feature switch).

Schema Problem.

Example 403 body
{
  "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"
}

410 Cursor expired; download a fresh snapshot.

Schema Problem.

Example 410 body
{
  "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"
}

426 App version below the minimum; block the UI with an update prompt. Pending outbox ops are kept.

Schema Problem.

Example 426 body
{
  "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"
}

429 Rate limited per device / user. Respect `Retry-After`.

Schema Problem.

Example 429 body
{
  "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
}

500 Unexpected server error; retry with backoff (writes are idempotent).

Schema Problem.

Example 500 body
{
  "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"
}