Delta sync: upserts and tombstones since a cursor
Operation
getFarmerChanges- 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
farmertombstones with reasonOUT_OF_SCOPE.
Error codes. CURSOR_EXPIRED
Parameters
| Name | In | Type | Required | Meaning | Example |
|---|---|---|---|---|---|
X-App-Version | header | string | Yes | App version `major.minor.patch+build`. Too old -> 426 `VERSION_TOO_OLD`. · pattern ^\d+\.\d+\.\d+(\+\d+)?$ | 2.0.0+200 |
X-Device-Id | header | string (uuid) | Yes | App-scoped device UUID (not ANDROID_ID). | 6b1f2c3d-0000-4000-8000-00000000d001 |
X-Request-Id | header | string (uuid) | No | Client-generated request id for tracing; echoed in responses and problems. | 0e0c5a1b-0000-4000-8000-0000000000a1 |
Accept-Language | header | string | No | Language for messages and labels. · one of en, te, ta; default "en" | te |
X-Tenant | header | string | No | Tenant code; must match the token. Urvi: `ap.gov`. · one of ap.gov | ap.gov |
cursor | query | string | Yes | Cursor from the snapshot's `deltaCursor` or the previous changes page. · max length 512 | chg_eyJ2IjoxMjM0NX0 |
scope | query | string | No | Same scope as the snapshot; defaults to all the user's scopes. · pattern ^(vo:\d+|unit|self)$ | vo:4101 |
pageSize | query | integer | No | Items per page. · ≥ 1; ≤ 500; default 100 | 100 |
Request
No body.
Responses
200 Changes page.
Schema ChangesPage.
{
"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"
}| Field | Type | Required | Rules | Meaning |
|---|---|---|---|---|
upserts | ChangeUpsert[] | Yes | ||
tombstones | Tombstone[] | Yes | ||
page | PageInfo | Yes | ||
cursor | string | Yes | Store and resend; valid for at least 30 days (`CURSOR_EXPIRED` / 410 -> re-snapshot). |
400 Malformed request or schema violation.
Schema Problem.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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"
}