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
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
field_visits | field_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
| Action | Needs | What it does |
|---|---|---|
field_service.advance | field_service:write | Update the visit |
field_service.cancel_visit | field_service:write | Call off the visit |
field_service.complete | field_service:write | Finish the visit |
field_service.dispatch | field_service:write | Dispatch a visit |
field_service.reassign | field_service:write | Send somebody else |
field_service.advance
Update the visit — On the way, arrived, work started — in that order.
Needs field_service:write · changes data
https://app.getera.io/api/v1/field_visits/{id}/actions/advance- Canonical address:
POST /api/v1/actions/field_service.advance
Input
| Field | Type | Required | Notes |
|---|---|---|---|
visitId | uuid | yes | Filled from the path {id} · Visit · id of field_visit |
to | string (enum) | yes | Now · Allowed values: en_route, arrived, in_progress |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
visit_not_found | The visit must exist. |
visit_closed | The visit must still be open. |
wrong_order | A 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"}'{
"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"{
"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
https://app.getera.io/api/v1/field_visits/{id}/actions/cancel_visit- Canonical address:
POST /api/v1/actions/field_service.cancel_visit
Input
| Field | Type | Required | Notes |
|---|---|---|---|
visitId | uuid | yes | Filled from the path {id} · Visit · id of field_visit |
note | string | Why · max 1000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
visit_not_found | The visit must exist. |
visit_closed | The 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"}'{
"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"{
"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
https://app.getera.io/api/v1/field_visits/{id}/actions/complete- Canonical address:
POST /api/v1/actions/field_service.complete
Input
| Field | Type | Required | Notes |
|---|---|---|---|
visitId | uuid | yes | Filled from the path {id} · Visit · id of field_visit |
notes | string | What was done · max 4000 | |
signatureFileId | uuid | Signature · id of file | |
photoFileIds | object / array | Photos · [fileId, …] |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
visit_not_found | The visit must exist. |
visit_closed | The visit must still be open. |
not_started | Somebody must have arrived. |
signature_required | A signature, when the business asks for one. |
file_not_found | Every 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"}'{
"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"{
"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)
https://app.getera.io/api/v1/field_visits/actions/dispatch- Canonical address:
POST /api/v1/actions/field_service.dispatch
Input
| Field | Type | Required | Notes |
|---|---|---|---|
appointmentId | uuid | yes | Appointment · id of appointment |
memberUserId | uuid | Who goes | |
addressId | uuid | Address · id of address |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
appointment_not_found | The appointment must exist. |
appointment_closed | The appointment must be pending or confirmed. |
already_dispatched | A booking has one visit. |
address_not_found | A chosen address must be in the guest’s address book. |
not_home_visit | The booking must have an address. |
outside_service_area | The address must be inside the service area. |
provider_cannot_perform | Who 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"}'{
"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"{
"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
https://app.getera.io/api/v1/field_visits/{id}/actions/reassign- Canonical address:
POST /api/v1/actions/field_service.reassign
Input
| Field | Type | Required | Notes |
|---|---|---|---|
visitId | uuid | yes | Filled from the path {id} · Visit · id of field_visit |
memberUserId | uuid | yes | Who goes |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
visit_not_found | The visit must exist. |
visit_closed | The visit must still be open. |
provider_cannot_perform | Who 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"}'{
"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"{
"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_visitsGET /api/v1/field_visits/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
appointment_id | string (uuid) · uuid | appointment | ||
member_user_id | string (uuid) · uuid | yes | app_user | |
status | string · text | dispatched, en_route, arrived, in_progress, completed, cancelled | ||
address | string · text | |||
address_id | string (uuid) · uuid | yes | address | |
departed_at | string (date-time) · timestamptz | yes | ||
arrived_at | string (date-time) · timestamptz | yes | ||
started_at | string (date-time) · timestamptz | yes | ||
completed_at | string (date-time) · timestamptz | yes | ||
cancelled_at | string (date-time) · timestamptz | yes | ||
cancellation_note | string · text | yes | ||
completion_notes | string · text | yes | ||
signature_file_id | string (uuid) · uuid | yes | file | |
photo_file_ids | string[] · uuid[] | |||
owner_user_id | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (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.
| Event | What happens | Payload |
|---|---|---|
field_service.arrived | Arrived at the address. | id, appointmentId, customerId, memberUserId |
field_service.completed | The visit is done. | id, appointmentId, customerId |
field_service.dispatched | A home visit was put on somebody’s route. | id, appointmentId, customerId, customerName, address, memberUserId, appointmentDate, startTime |
field_service.en_route | On the way — the customer-notification hook. | id, appointmentId, customerId, memberUserId |
field_service.reassigned | Somebody else is going. | id, appointmentId, memberUserId |
field_service.started | Work started. | id, appointmentId, customerId, memberUserId |
field_service.visit_cancelled | A visit was called off. | id, appointmentId |