Asset servicing
Servicing is how Loca.fi tracks maintenance work performed on tracked assets — an inspection, calibration, repair, clean, or a delivery/pickup job. You define what can be serviced once (a service definition), then record each visit as a service record with the actual items scanned against it.
Servicing is an asset-management feature. It exists for tenants that track individual assets (equipment, tools, returnable containers) through maintenance and delivery cycles. Retail tenants typically don't use the Services domain at all.
Read Service records first for the
Open → InProgress → Complete/Cancelled state machine — this guide is the
doing, that page is the understanding.
Overview
Three entities, one flow:
- Service type — a category (
{Type, SystemType}). Six system types are auto-seeded per organisation (DeliverGoods,PickupGoods,DeliverSku,PickupSku,DeliverItem,PickupItem); create custom ones withcreateservicetype. - Service definition (
ServiceMaster) — the catalogue entry, "a SKU for a job": aServiceCode, a service type, and optionally the specific item or SKU it applies to —createservice. - Service record — one performed (or scheduled) visit against a
definition, with service items (the assets involved) attached —
createservicerecord.
Definitions and types share the ServiceMaster permission module;
records and their item lines are gated on ServiceRecord.
There is no interval/recurrence field on a service definition — cadence is
expressed per record via ScheduledDate, and recurring due/overdue
tracking is the event-schedule feature (see
Notifications & alerts).
Prerequisites
- A token with
ServiceMasterandServiceRecordpermissions — see Getting started. - Items with tags already onboarded (or let the record auto-create them — step 4).
- Optional: service extended properties. Task/service custom fields are
template-less — create them with
TemplateType: "Service"and attach values directly; see Templates & extended properties.
Step-by-step
1. Pick or create a service type
List what's already there (the six seeded system types plus any custom)
with getfilteredservicetypes,
or create a custom category:
curl -sk -X POST "http://<host>/api/servicetypes/createservicetype" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Type":"Annual inspection"}'
Custom types are always created SystemType: false. Pick a system
Deliver/Pickup type instead when completing the record should move the
serviced items to a destination place (step 5).
2. Define the service (ServiceMaster)
curl -sk -X POST "http://<host>/api/services/createservice" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"ServiceCode":"INSP-FORKLIFT","Description":"Annual forklift inspection",
"ServiceTypeId":"<serviceTypeId>","SkuId":"<forkliftSkuId>"}'
Returns 200 with a ServiceMasterSummaryDto — capture the Id.
ItemId and SkuId are optional and mutually exclusive: set ItemId
to scope the definition to one specific asset, SkuId to a whole asset
type, or neither for a general job. System types constrain the link —
DeliverItem/PickupItem require an ItemId, DeliverSku/PickupSku a
SkuId.
Manage definitions with
getfilteredservices (OData),
getservice/{id},
updateservice (full-replace),
and deleteservice/{id}; a
lean picker list is at
get-filtered-services-for-lookup.
3. Open a service record for the visit
curl -sk -X POST "http://<host>/api/servicerecords/createservicerecord" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"ServiceNumber":"SRV-2026-0042","ServiceMasterId":"<serviceMasterId>",
"RequestedQty":3,"ScheduledDate":"2026-07-21T09:00:00Z",
"Notes":"July inspection round","MoveItemsOnComplete":false}'
Returns 200 with a ServiceRecordDetailDto in status "Open" — unless
you supplied ServiceItems inline, in which case it starts "InProgress".
Notable fields:
ServiceNumber— unique among non-terminal records; completing or cancelling a record frees its number for reuse.FromPlaceId/ToPlaceId— the movement leg.ToPlaceIdis required when the service type is a system Deliver/Pickup type orMoveItemsOnCompleteistrue.RequestedQty/AdjustedQty/ManualQty— planned/corrected counts (must be ≥ 0).ScannedQtyis server-maintained from the item lines — don't try to set it.ReasonId— must reference a reason with theServicescope.ScheduledDate— when the visit is due; clipped to whole seconds.
4. Attach the serviced assets (service items)
As the technician scans tags (or you know the item ids), add lines with
createserviceitem:
curl -sk -X POST "http://<host>/api/serviceitems/createserviceitem" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"ServiceRecordId":"<recordId>","AutoCreateItems":true,
"CreatePlaceId":"<workshopPlaceId>",
"ServiceItems":[
{"TagNumber":"E280116060000209DC5A0AB1","Notes":"Brake pads replaced"},
{"ItemId":"<knownItemId>","Attribute1":"Pass"}]}'
Each line resolves by ItemId or TagNumber. An unknown tag is
auto-created as a new item at CreatePlaceId (default: the
organisation's root place) when AutoCreateItems is true (the default);
with AutoCreateItems: false an unresolved tag is silently skipped —
no line, no error. Every add/delete bumps the record's ScannedQty and any
mutation bumps an Open record to InProgress.
Per-line housekeeping:
updateserviceitem
(notes, attributes, extended properties — not the item/tag reference),
deleteserviceitem/{id},
bulk deleteserviceitems
(partial-success: bad lines accumulate errors while good lines delete).
Photos as proof-of-service attach via the record's
uploadmedia or the line-level
equivalent.
Raw scan capture without item resolution is also available —
updateservicerecordtags
adds/removes plain tag-number scans (ServiceRecordTags, de-duplicated),
separate from the resolved item lines.
5. Complete — or cancel — the record
curl -sk -X POST "http://<host>/api/servicerecords/completeservicerecord" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Id":"<recordId>","CompleteTime":"2026-07-21T11:30:00Z"}'
Sets Status: "Complete" and stamps CompletedOn (omit CompleteTime to
use the server's current time). If MoveItemsOnComplete is true — or the
service type is a system Deliver/Pickup type — completion moves every
attached item to ToPlaceId, writing movement history and setting item
state to Moved (see Item lifecycle).
Completion also auto-completes a linked system-type task action — see
Tasks & schedules for driving servicing through
assigned tasks.
cancelservicerecord
abandons the visit with no item movement. Both terminal states lock the
record against further edits (re-issuing the same transition is an
idempotent 200 no-op); only
deleteservicerecord/{id}
works from any state. Full matrix:
Service records.
Query history and due-for-service
Records —
getfilteredservicerecords
is the OData list (ServiceRecordSummaryDto: status, quantities, places,
ScheduledDate, CompletedOn). Due-for-service is a filter over open
records past their scheduled date:
curl -sk "http://<host>/api/servicerecords/getfilteredservicerecords?%24filter=Status%20eq%20%27Open%27%20and%20ScheduledDate%20le%202026-07-07T00:00:00Z" \
-H "Authorization: Token $TOKEN"
Per-asset history —
getfiltereditemservicerecords
returns an item-centric view (one row per item × record,
ItemServiceRecordSummaryDto = the record fields plus
ItemId/ItemName/ItemTagNumber/ItemAssetNumber and SKU columns).
Filter %24filter=ItemId eq <guid> for one asset's full service history.
Item lines — getfilteredserviceitems
lists lines across records;
getserviceitem/{id}
returns one with its extended properties and media.
For recurring cadence ("every asset must be cleaned monthly") and overdue alerting, use event schedules and monitors rather than polling — Notifications & alerts.
Reporting and CSV export
- CSV of the filtered record list —
exportfilteredservicerecords; item lines —exportfilteredserviceitems; definitions —exportfilteredservices. - Advanced search's single query grammar also covers servicing —
advancedsearch/servicerecordsandadvancedsearch/servicerecorditemstake a rule tree + sorting/paging and feed the saved-report / scheduled emailed-report pipeline.
Error handling
All failures follow the platform-wide
error and response conventions —
400 with a JSON array of { ErrorCode, Message, Field }.
| Situation | Response |
|---|---|
Missing ServiceCode (definition) or ServiceMasterId (record) | 400 MissingRequiredField |
Unknown ServiceTypeId, ServiceMasterId, FromPlaceId, ToPlaceId, or non-Service-scope ReasonId | 400 InvalidReference |
Both ItemId and SkuId set on a definition — or the one a system type requires missing | 400 InvalidReference "Item or SKU value needs to be provided" |
ServiceNumber already used by a non-terminal record | 400 DuplicateResource |
Missing ToPlaceId with MoveItemsOnComplete or a system Deliver/Pickup type | 400 InvalidValue "ToPlaceId" |
| Negative quantity | 400 InvalidValue |
Mutating a Complete/Cancelled record (update, tags, items, media) | 400 InvalidOperation "Status" "Record cannot be updated" |
Complete → Cancel (or vice versa) | 400 InvalidOperation "Status" — terminal states can't cross |
| Get record by unknown id | 400 "Service record not found" (ErrorCode collapses to InvalidOperation; the Message carries the signal) |
Service-item line resolving to a different record than ServiceRecordId | 400 InvalidReference (bulk delete: accumulated per line, others still succeed) |
No token / wrong scheme (Bearer) | 401 |
Related
- Service records — the state machine behind step 5.
- Item lifecycle — the
Movedstate and movement history written on completion. - Tasks & schedules — assign servicing work to users; completing a record auto-ticks the linked task action (see also Tasks & schedules concepts).
- Notifications & alerts — event schedules/monitors for recurring service cadence and overdue alerts.
- Templates & extended properties — service extended properties are template-less custom fields.
- Reference pages: Create Service Record, Update Service Record, Complete, Cancel, Add Service Items, Create Service Definition, Create Service Type.