ReferenceReference by module

Home service API

Every Home service action (5) and data model (1): endpoints, scopes, input, preconditions, events and examples.

Visits at the customer’s address — who goes, when they arrive, signature and photos.

Module code field_service · 5 actions · 1 data models · 7 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: field_service (everything), field_service:read (read only) or field_service:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
field_visitsfield_visit/api/v1/field_visits

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
field_service.advancefield_service:writeUpdate the visit
field_service.cancel_visitfield_service:writeCall off the visit
field_service.completefield_service:writeFinish the visit
field_service.dispatchfield_service:writeDispatch a visit
field_service.reassignfield_service:writeSend somebody else

field_service.advance

Update the visit — On the way, arrived, work started — in that order.

Needs field_service:write · changes data

POSThttps://app.getera.io/api/v1/field_visits/{id}/actions/advance
API key or OAuth token · field_service:write
  • Canonical address: POST /api/v1/actions/field_service.advance

Input

FieldTypeRequiredNotes
visitIduuidyesFilled from the path {id} · Visit · id of field_visit
tostring (enum)yesNow · Allowed values: en_route, arrived, in_progress

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
visit_not_foundThe visit must exist.
visit_closedThe visit must still be open.
wrong_orderA visit only moves forward.

Events it publishes: field_service.en_route, field_service.arrived, field_service.started

What it writes: field_visit (update) — Moves the status and stamps the time

curl -X POST https://app.getera.io/api/v1/field_visits/FIELD_VISIT_ID/actions/advance \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"to":"en_route"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "field_service.en_route",
    "field_service.arrived",
    "field_service.started"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fieldVisits.advance("FIELD_VISIT_ID", {
  to: "en_route"
})
era field_visits advance FIELD_VISIT_ID \
  --to "en_route"
tools/call
{
  "name": "era__field_service__advance",
  "arguments": {
    "visitId": "FIELD_VISIT_ID",
    "to": "en_route"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold field_service:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 visit_not_found, visit_closed, wrong_order · 429 slow down

field_service.cancel_visit

Call off the visit — Takes a visit off the route. The booking itself is not cancelled.

Needs field_service:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/field_visits/{id}/actions/cancel_visit
API key or OAuth token · field_service:write
  • Canonical address: POST /api/v1/actions/field_service.cancel_visit

Input

FieldTypeRequiredNotes
visitIduuidyesFilled from the path {id} · Visit · id of field_visit
notestringWhy · max 1000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
visit_not_foundThe visit must exist.
visit_closedThe visit must still be open.

Events it publishes: field_service.visit_cancelled

What it writes: field_visit (update) — Moves it to cancelled

curl -X POST https://app.getera.io/api/v1/field_visits/FIELD_VISIT_ID/actions/cancel_visit \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"note":"Why"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "field_service.visit_cancelled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fieldVisits.cancelVisit("FIELD_VISIT_ID", {
  note: "Why"
})
era field_visits cancel_visit FIELD_VISIT_ID \
  --note "Why"
tools/call
{
  "name": "era__field_service__cancel_visit",
  "arguments": {
    "visitId": "FIELD_VISIT_ID",
    "note": "Why"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold field_service:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 visit_not_found, visit_closed · 429 slow down

field_service.complete

Finish the visit — Closes the visit with notes, the customer’s signature and photos.

Needs field_service:write · changes data

POSThttps://app.getera.io/api/v1/field_visits/{id}/actions/complete
API key or OAuth token · field_service:write
  • Canonical address: POST /api/v1/actions/field_service.complete

Input

FieldTypeRequiredNotes
visitIduuidyesFilled from the path {id} · Visit · id of field_visit
notesstringWhat was done · max 4000
signatureFileIduuidSignature · id of file
photoFileIdsobject / arrayPhotos · [fileId, …]

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
visit_not_foundThe visit must exist.
visit_closedThe visit must still be open.
not_startedSomebody must have arrived.
signature_requiredA signature, when the business asks for one.
file_not_foundEvery file must exist.

Events it publishes: field_service.completed

What it writes: field_visit (update) — Completes it with its proof of work

curl -X POST https://app.getera.io/api/v1/field_visits/FIELD_VISIT_ID/actions/complete \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"notes":"What was done"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "field_service.completed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fieldVisits.complete("FIELD_VISIT_ID", {
  notes: "What was done"
})
era field_visits complete FIELD_VISIT_ID \
  --notes "What was done"
tools/call
{
  "name": "era__field_service__complete",
  "arguments": {
    "visitId": "FIELD_VISIT_ID",
    "notes": "What was done"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold field_service:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 visit_not_found, visit_closed, not_started, signature_required, file_not_found · 429 slow down

field_service.dispatch

Dispatch a visit — Puts a home-service booking on somebody’s route, at an address inside the service area.

Needs field_service:write · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/field_visits/actions/dispatch
API key or OAuth token · field_service:write
  • Canonical address: POST /api/v1/actions/field_service.dispatch

Input

FieldTypeRequiredNotes
appointmentIduuidyesAppointment · id of appointment
memberUserIduuidWho goes
addressIduuidAddress · id of address

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
appointment_not_foundThe appointment must exist.
appointment_closedThe appointment must be pending or confirmed.
already_dispatchedA booking has one visit.
address_not_foundA chosen address must be in the guest’s address book.
not_home_visitThe booking must have an address.
outside_service_areaThe address must be inside the service area.
provider_cannot_performWho goes must deliver one of the services.

Events it publishes: field_service.dispatched

What it writes: field_visit (insert) — Opens the visit

curl -X POST https://app.getera.io/api/v1/field_visits/actions/dispatch \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"appointmentId":"APPOINTMENT_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "field_service.dispatched"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fieldVisits.dispatch({
  appointmentId: "APPOINTMENT_ID"
})
era field_visits dispatch \
  --appointment-id "APPOINTMENT_ID"
tools/call
{
  "name": "era__field_service__dispatch",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold field_service:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 appointment_not_found, appointment_closed, already_dispatched, address_not_found, not_home_visit, outside_service_area, provider_cannot_perform · 429 slow down

field_service.reassign

Send somebody else — Gives an open visit to another member who delivers the service.

Needs field_service:write · changes data

POSThttps://app.getera.io/api/v1/field_visits/{id}/actions/reassign
API key or OAuth token · field_service:write
  • Canonical address: POST /api/v1/actions/field_service.reassign

Input

FieldTypeRequiredNotes
visitIduuidyesFilled from the path {id} · Visit · id of field_visit
memberUserIduuidyesWho goes

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
visit_not_foundThe visit must exist.
visit_closedThe visit must still be open.
provider_cannot_performWho goes must deliver one of the services.

Events it publishes: field_service.reassigned

What it writes: field_visit (update) — Changes who goes

curl -X POST https://app.getera.io/api/v1/field_visits/FIELD_VISIT_ID/actions/reassign \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"memberUserId":"MEMBER_USER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "field_service.reassigned"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fieldVisits.reassign("FIELD_VISIT_ID", {
  memberUserId: "MEMBER_USER_ID"
})
era field_visits reassign FIELD_VISIT_ID \
  --member-user-id "MEMBER_USER_ID"
tools/call
{
  "name": "era__field_service__reassign",
  "arguments": {
    "visitId": "FIELD_VISIT_ID",
    "memberUserId": "MEMBER_USER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold field_service:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 visit_not_found, visit_closed, provider_cannot_perform · 429 slow down

Data models

FieldVisit

Table field_visit · Resource field_visits

  • GET /api/v1/field_visits
  • GET /api/v1/field_visits/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
appointment_idstring (uuid) · uuidappointment
member_user_idstring (uuid) · uuidyesapp_user
statusstring · textdispatched, en_route, arrived, in_progress, completed, cancelled
addressstring · text
address_idstring (uuid) · uuidyesaddress
departed_atstring (date-time) · timestamptzyes
arrived_atstring (date-time) · timestamptzyes
started_atstring (date-time) · timestamptzyes
completed_atstring (date-time) · timestamptzyes
cancelled_atstring (date-time) · timestamptzyes
cancellation_notestring · textyes
completion_notesstring · textyes
signature_file_idstring (uuid) · uuidyesfile
photo_file_idsstring[] · uuid[]
owner_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Unique: appointment_id

Checks:

  • CHECK ((length(btrim(address)) > 0))
  • CHECK (((status = 'cancelled'::text) = (cancelled_at IS NOT NULL)))
  • CHECK (((status = 'completed'::text) = (completed_at IS NOT NULL)))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
field_service.arrivedArrived at the address.id, appointmentId, customerId, memberUserId
field_service.completedThe visit is done.id, appointmentId, customerId
field_service.dispatchedA home visit was put on somebody’s route.id, appointmentId, customerId, customerName, address, memberUserId, appointmentDate, startTime
field_service.en_routeOn the way — the customer-notification hook.id, appointmentId, customerId, memberUserId
field_service.reassignedSomebody else is going.id, appointmentId, memberUserId
field_service.startedWork started.id, appointmentId, customerId, memberUserId
field_service.visit_cancelledA visit was called off.id, appointmentId
Was this page helpful?