Patrol Sync¶
Recommendations for synchronizing patrol-related data for offline use. Aimed at mobile clients that want to minimize downloads and the number of checks to detect changes.
This guide covers patrol types (the catalog of types, like “Foot”, “Vehicle”), the tracked-by schema (who can be reported as segment leader), and patrols and patrol segments (the actual patrol instances and their segments). Only patrol types and the tracked-by schema support conditional GET; patrol and segment list/detail do not. Patrol types support both ETag / If-None-Match and Last-Modified / If-Modified-Since; the tracked-by schema supports ETag / If-None-Match only.
Note: The tracked-by schema ETag is per-user — it is salted with the requesting user’s ID (and the user-profile header when present), because the set of selectable leaders depends on the caller’s permissions. Cache it per authenticated identity; do not share a stored ETag between users or profiles. Patrol-type ETags are not user-salted: the catalog is identical for every user, so a stored patrol-type ETag is shareable across users and profiles.
Note: When you send both If-None-Match and If-Modified-Since, the server evaluates If-None-Match and ignores If-Modified-Since (RFC 9110 §13.2.2). Prefer If-None-Match — it detects changes that a one-second-resolution timestamp can miss.
Note: The web UI currently supports only one patrol segment per patrol. The API allows multiple segments per patrol, but clients that aim for parity with the web UI should treat one segment per patrol as the supported case.
Patrol Types (catalog for offline)¶
Patrol types define the kinds of patrols (e.g. Foot, Vehicle). Sync the list for offline so the app can create patrols and show type metadata (display name, icon, priority, etc.).
Recommended: List with conditional GET¶
Use the patrol types list as the single source for the catalog. The response ETag reflects any change to the returned list. Use If-None-Match to avoid re-downloading when nothing has changed.
The list also sends Last-Modified, and If-Modified-Since is supported. Last-Modified is the newest updated_at across all patrol types — including deactivated ones, which the default list hides — so deactivating a type still advances it. It can therefore report “modified” when your filtered view is in fact unchanged; If-None-Match is the more precise check and is preferred.
Endpoint
GET /api/v1.0/activity/patrols/types
Query parameters
Parameter |
Description |
|---|---|
|
When |
|
When |
|
ISO 8601 date/time. Return only types whose |
Each distinct combination of query parameters has its own ETag, so store the ETag alongside the exact request you made.
Optimizing “do I need to download?”
Store the ETag from the last successful list response, keyed by the query parameters you sent.
On the next sync, repeat the same URL and query parameters and send If-None-Match: your stored ETag value.
If the server responds 304 Not Modified, nothing changed — skip downloading the body and keep your cached patrol types.
If the server responds 200 OK, replace your cache with the new response and store the new ETag.
This minimizes both checks (one conditional GET) and downloads (no body when unchanged).
Optimizing full download
Initial / full sync: Call the list once. The list is small and metadata-only, so one request is enough. Add
include_inactive=trueif your client needs to display or reconcile deactivated types (e.g. to label historical patrols).
Single patrol type (detail)¶
GET /api/v1.0/activity/patrols/types/<patrol_type_id_or_value>
The path segment accepts either the patrol type UUID or its unique value slug — one route serves both, matching the v2 event-types endpoint. (The route regex is DRF’s default [^/.]+, so a value containing / or . is not addressable here; the same constraint applies to v2.)
Supports conditional GET via ETag / If-None-Match and via Last-Modified / If-Modified-Since, where Last-Modified is that type’s own updated_at. Use when you need to refresh or check a single type; for full catalog sync, the list endpoint with conditional GET is simpler.
The include_schema parameter is supported here as well as on the list, with the same empty-envelope semantics. include_schema=true yields a different ETag than the no-param request, so the two response shapes never share a cache entry.
Like the list, the detail route hides deactivated types by default: a GET for a type with is_active=false returns 404 Not Found unless you pass ?include_inactive=true. The write methods (PUT, PATCH, DELETE) are unaffected and always reach the type, deactivated or not.
Patrol type response fields¶
Field |
Writable |
Description |
|---|---|---|
|
No |
UUID primary key. |
|
Yes |
Unique string identifier for this type (e.g. |
|
Yes |
Human-readable label. |
|
Yes |
Sort order hint. Nullable. |
|
Yes |
Raw stored icon identifier. Empty string when no explicit icon has been set. |
|
No |
Resolved icon identifier. Equals |
|
Yes |
Default priority for patrols of this type. |
|
Yes |
Whether this type is available for new patrols. |
Patrol type write endpoints¶
Method |
URL |
Description |
|---|---|---|
|
|
Create a patrol type. |
|
|
Replace a patrol type. |
|
|
Partially update a patrol type. |
|
|
Delete a patrol type. |
<id> in the table above also accepts the type’s unique value slug in place of the UUID — the same dual id/value lookup as the GET detail endpoint applies to PUT, PATCH, and DELETE.
value must be unique per tenant. Attempting to create a duplicate returns 400 Bad Request. Required permissions: add_patroltype, change_patroltype, or delete_patroltype respectively.
Tracked-by schema (segment leaders / “reported by”)¶
When creating or editing patrol segments, clients often need the list of subjects (and optionally users) that can be selected as segment leader (“tracked by”). This is provided by the tracked-by schema endpoint. It has an ETag so you can avoid re-downloading when the set of possible leaders has not changed.
Endpoint
GET /api/v1.0/activity/patrols/trackedby
Returns metadata describing the schema for “tracked by” (e.g. subject list). The ETag is derived from the set of subjects available to the user. Use If-None-Match for conditional GET; 304 Not Modified means the schema/options are unchanged.
Optimizing “do I need to download?”
Store the ETag from the last successful response.
On the next sync, send If-None-Match: your stored ETag value.
304 — keep your cached schema; 200 — replace cache and store the new ETag.
Patrols (list and detail)¶
Patrols are the actual patrol instances (with one or more segments). The list and detail endpoints do not support ETag or Last-Modified. Use query parameters to limit results and paginate; for incremental-style sync you can use the filter with a date_range to fetch patrols overlapping a time window.
Endpoints
GET /api/v1.0/activity/patrols
GET /api/v1.0/activity/patrols/<patrol_id>
List query parameters
Parameter |
Description |
|---|---|
|
JSON object for advanced filtering. Example: |
|
If |
|
Filter by state(s). Allowed values depend on |
Responses are paginated. There is no ETag on the list or detail; you cannot use conditional GET to skip re-downloading. To minimize data transfer, use filter (e.g. date_range) and pagination, and store the last sync time or last requested range for incremental pulls.
Single patrol
Detail returns one patrol by UUID. No ETag; refetch when you need the latest state.
Patrol request payloads (POST, PATCH, PUT)¶
Create patrol: POST /api/v1.0/activity/patrols
Update patrol: PATCH or PUT /api/v1.0/activity/patrols/<patrol_id>
Request body (JSON). All fields are optional on create; omit fields you are not changing on update.
Field |
Type |
Description |
|---|---|---|
|
string |
Patrol objective (text). Can be blank/null. |
|
integer |
Priority. Allowed values: |
|
string |
Patrol state: |
|
string |
Patrol title. Max 255 characters. Can be blank/null. |
|
array |
List of note objects. Each note: |
|
array |
List of segment objects (see Patrol segment request payloads below). When creating a patrol you can include segments in this array; do not send |
Validation: scheduled_start must be earlier than scheduled_end in any segment. Patrol state may be auto-set to "done" when all segments have ended.
Patrol segments (list and detail)¶
Patrol segments are the legs of a patrol (each has a type, time range, leader, etc.). The web UI supports only one segment per patrol; the API may return or accept multiple segments per patrol, but the web UI does not. List and detail do not support ETag or Last-Modified.
Endpoints
GET /api/v1.0/activity/patrols/segments
GET /api/v1.0/activity/patrols/segments/<segment_id>
List is paginated. Use when syncing segment data; no conditional GET is available.
Segment–events
GET /api/v1.0/activity/patrols/segments/<patrol_segment_id>/events
Returns events linked to a patrol segment. No ETag.
Patrol segment request payloads (POST, PATCH, PUT)¶
Segments can be created or updated in two ways: nested in a patrol (in patrol_segments when posting to POST /patrols or PATCH/PUT /patrols/<id>) or standalone via the segments list.
Create segment (standalone): POST /api/v1.0/activity/patrols/segments
Update segment (standalone): PATCH or PUT /api/v1.0/activity/patrols/segments/<segment_id>
When nested in a patrol payload, omit patrol (the server sets it). When posting to the segments endpoint, patrol is required (patrol UUID).
Field |
Type |
Required (standalone) |
Description |
|---|---|---|---|
|
UUID |
Yes (standalone only) |
Patrol ID. Omit when segment is nested in a patrol body. |
|
string |
No |
Patrol type |
|
object |
No |
Segment leader (who is tracked). Object: |
|
string |
No |
ISO 8601 date/time. Scheduled start. |
|
string |
No |
ISO 8601 date/time. Scheduled end. Must be after |
|
object |
No |
Actual patrol time. |
|
object |
No |
Start point. |
|
object |
No |
End point. Same shape as |
|
UUID |
No |
Include when updating an existing segment (nested or standalone). |
Read-only in responses (do not send when creating/updating): image_url, icon_id, events.
Patrol sub-resources (notes, files)¶
Patrol notes and files are nested under a patrol:
GET /api/v1.0/activity/patrols/<patrol_id>/notes
GET /api/v1.0/activity/patrols/<patrol_id>/notes/<note_id>
GET /api/v1.0/activity/patrols/<patrol_id>/files
GET /api/v1.0/activity/patrols/<patrol_id>/files/...
These endpoints do not expose ETag or Last-Modified. Refetch as needed.
Note request payloads: Create note with POST .../patrols/<patrol_id>/notes with body { "text": "..." }. Update with PATCH/PUT .../notes/<note_id>. The patrol is implied by the URL.
File request payloads: Create/update patrol files via the files endpoint; the payload typically includes usercontent_id, usercontent_type, and optionally comment, ordernum. See the API or schema for the full file upload format.
Summary for mobile clients¶
Goal |
Recommendation |
|---|---|
Fewest “has anything changed?” checks |
Use conditional GET only where supported: patrol types list and tracked-by schema. Send If-None-Match. |
Patrol types catalog |
GET …/patrols/types with If-None-Match; treat 304 as “no download needed”. One list call is enough for the full catalog. Returns active types only by default — add |
Tracked-by / segment leaders |
GET …/patrols/trackedby with If-None-Match; 304 means schema/options unchanged. |
Patrols list |
No ETag. Use filter (e.g. date_range with |
Patrol and segment detail |
No ETag. Refetch when you need the latest state. |
Where conditional GET works |
Patrol types (list and single), via ETag / If-None-Match or Last-Modified / If-Modified-Since; tracked-by schema, via ETag / If-None-Match only. Prefer If-None-Match everywhere. Patrols and segments support neither. |