DevelopersAPI reference

Time API

REST endpoints for time in ServFlo: 17 operations with parameters, request and response fields, permissions and curl examples.

Base URL: https://app.servflo.ai/api. Every call needs Authorization: Bearer sfk_… (Authentication); {organizationId} ($ORGANIZATION_ID in the examples) is your business's id.

OperationEndpointRisk
Labour time on a jobGET /organizations/{organizationId}/jobs/{jobId}/timeread
Your clock: clocked in, on a break, today's timeGET /organizations/{organizationId}/time/clockread
Clock inPOST /organizations/{organizationId}/time/clock-inwrite
Clock outPOST /organizations/{organizationId}/time/clock-outwrite
Start a breakPOST /organizations/{organizationId}/time/break/startwrite
End a breakPOST /organizations/{organizationId}/time/break/endwrite
Start non-job timePOST /organizations/{organizationId}/time/activity/startwrite
End non-job timePOST /organizations/{organizationId}/time/activity/endwrite
List time entriesGET /organizations/{organizationId}/time-entriesread
Add a time entry by handPOST /organizations/{organizationId}/time-entrieswrite
Edit a time entryPATCH /organizations/{organizationId}/time-entries/{entryId}write
Delete a time entryDELETE /organizations/{organizationId}/time-entries/{entryId}sensitive
Everyone's weekGET /organizations/{organizationId}/timesheetsread
One person's weekGET /organizations/{organizationId}/timesheets/{userId}read
Submit your weekPOST /organizations/{organizationId}/timesheets/submitwrite
Approve or reject a person's weekPOST /organizations/{organizationId}/timesheets/{userId}/reviewwrite
Export a week as CSVGET /organizations/{organizationId}/timesheets/exportread

Labour time on a job

GET /organizations/{organizationId}/jobs/{jobId}/time

Job and travel minutes per person on the job, the totals, and the entries. Technicians see their own time on their own jobs. Read-only.

Permission: time.read (Owner, Admin, Dispatcher, Technician (own time)); risk read (OAuth scope servflo.read).

Permissiontime.read
Riskread, OAuth scope servflo.read. Read: reads only.
MCP tooljobTime_get (toolset scheduling)

Parameters

NameInTypeRequiredDescription
jobIdpathstringYes

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/jobs/{jobId}/time" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
peoplearray of object
people[].userIdstring
people[].namestring or null
people[].technicianobject or null
people[].technician.idstring
people[].technician.namestring
people[].technician.colorstring
people[].jobMinutesinteger
people[].travelMinutesinteger
people[].runningboolean
jobMinutesinteger
travelMinutesinteger
entriesarray of object
entries[].idstring
entries[].organizationIdstring
entries[].userIdstring
entries[].technicianIdstring or null
entries[].jobIdstring or null
entries[].kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
entries[].activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
entries[].startedAtstring (date-time)
entries[].endedAtstring (date-time) or null
entries[].notestring or null
entries[].sourceenumOne of CLOCK, MANUAL, AUTO.
entries[].statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entries[].editReasonstring or null
entries[].editedByUserIdstring or null
entries[].approvedByUserIdstring or null
entries[].approvedAtstring (date-time) or null
entries[].createdAtstring (date-time)
entries[].updatedAtstring (date-time)
entries[].jobobject or null
entries[].job.idstring
entries[].job.identifierstring
entries[].technicianobject or null
entries[].technician.idstring
entries[].technician.namestring
entries[].technician.colorstring
entries[].userNamestring or null
entries[].minutesintegerWhole minutes (a running entry counts to now).
entries[].datestringThe day it started, in the requested timezone.

Your clock: clocked in, on a break, today's time

GET /organizations/{organizationId}/time/clock

The caller's running entries (SHIFT, BREAK, JOB, TRAVEL) and today's minutes per kind and paid minutes, date being today in timeZone. Read-only.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP tooltimeClock_get (toolset team)

Parameters

NameInTypeRequiredDescription
datequerystringNoThe day, YYYY-MM-DD. Defaults to today in the organization's timezone.
timeZonequerystringNoIANA timezone days and weeks are counted in (e.g. America/Chicago). Defaults to the organization's timezone (Settings → General). Up to 100 characters.

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time/clock" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
clockedInboolean
onBreakboolean
activityenum or nullThe non-job time running now, if any. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
runningarray of object
running[].idstring
running[].organizationIdstring
running[].userIdstring
running[].technicianIdstring or null
running[].jobIdstring or null
running[].kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
running[].activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
running[].startedAtstring (date-time)
running[].endedAtstring (date-time) or null
running[].notestring or null
running[].sourceenumOne of CLOCK, MANUAL, AUTO.
running[].statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
running[].editReasonstring or null
running[].editedByUserIdstring or null
running[].approvedByUserIdstring or null
running[].approvedAtstring (date-time) or null
running[].createdAtstring (date-time)
running[].updatedAtstring (date-time)
running[].jobobject or null
running[].job.idstring
running[].job.identifierstring
running[].technicianobject or null
running[].technician.idstring
running[].technician.namestring
running[].technician.colorstring
running[].userNamestring or null
running[].minutesintegerWhole minutes (a running entry counts to now).
running[].datestringThe day it started, in the requested timezone.
todayobject
today.SHIFTinteger
today.JOBinteger
today.TRAVELinteger
today.BREAKinteger
today.ACTIVITYinteger
today.paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.

Clock in

POST /organizations/{organizationId}/time/clock-in

Starts the caller's SHIFT now. Already clocked in: returns the running shift (safe to retry). Emits time.clocked_in. Returns the shift entry.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeClock_clockIn (toolset team)

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time/clock-in" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

Clock out

POST /organizations/{organizationId}/time/clock-out

Ends the caller's SHIFT now, and any running break, job or travel time. 409 when not clocked in. Emits time.clocked_out. Returns the shift entry.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeClock_clockOut (toolset team)

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time/clock-out" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

Start a break

POST /organizations/{organizationId}/time/break/start

Starts a BREAK for the caller (they must be clocked in; 409 otherwise). Already on a break: returns it. Breaks are taken off paid time.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeClock_startBreak (toolset team)

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time/break/start" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

End a break

POST /organizations/{organizationId}/time/break/end

Ends the caller's running BREAK (409 when not on one). Returns the break entry.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeClock_endBreak (toolset team)

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time/break/end" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

Start non-job time

POST /organizations/{organizationId}/time/activity/start

Starts non-job work for the caller (ACTIVITY with activity: the shop, the supply house, a meeting, training, the vehicle, paperwork or other), as ServiceTitan's non-job events. Needs a running shift (409 otherwise). Stops a break, travel or another activity; the same activity again returns the running one. Heading to or starting a job ends it. Paid time, shown per activity on the timesheet.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeClock_startActivity (toolset team)

Request body

FieldTypeRequiredDescription
activityenumYesNon-job time: SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE (fuel, service), ADMIN (paperwork) or OTHER. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
notestring or nullNoUp to 500 characters.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time/activity/start" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"activity":"SHOP"}'

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

End non-job time

POST /organizations/{organizationId}/time/activity/end

Ends the caller's running ACTIVITY (409 when none). Returns the entry.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeClock_endActivity (toolset team)

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time/activity/end" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

List time entries

GET /organizations/{organizationId}/time-entries

Time entries newest first, filtered by person (userId), technician, job, kind, status and days (from/to, YYYY-MM-DD in timeZone), paged with limit/offset and a total. Technicians always get only their own entries. Read-only.

Permission: time.read (Owner, Admin, Dispatcher, Technician (own time)); risk read (OAuth scope servflo.read).

Permissiontime.read
Riskread, OAuth scope servflo.read. Read: reads only.
MCP tooltimeEntries_list (toolset team)

Parameters

NameInTypeRequiredDescription
userIdquerystringNoUp to 200 characters.
technicianIdquerystringNo
jobIdquerystringNo
kindqueryenumNoSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
statusqueryenumNoApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
fromquerystringNoA calendar date in the business's own calendar, YYYY-MM-DD.
toquerystringNoA calendar date in the business's own calendar, YYYY-MM-DD.
timeZonequerystringNoIANA timezone days and weeks are counted in (e.g. America/Chicago). Defaults to the organization's timezone (Settings → General). Up to 100 characters.
limitqueryintegerNo1–500.
offsetqueryintegerNo

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time-entries" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
itemsarray of object
items[].idstring
items[].organizationIdstring
items[].userIdstring
items[].technicianIdstring or null
items[].jobIdstring or null
items[].kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
items[].activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
items[].startedAtstring (date-time)
items[].endedAtstring (date-time) or null
items[].notestring or null
items[].sourceenumOne of CLOCK, MANUAL, AUTO.
items[].statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
items[].editReasonstring or null
items[].editedByUserIdstring or null
items[].approvedByUserIdstring or null
items[].approvedAtstring (date-time) or null
items[].createdAtstring (date-time)
items[].updatedAtstring (date-time)
items[].jobobject or null
items[].job.idstring
items[].job.identifierstring
items[].technicianobject or null
items[].technician.idstring
items[].technician.namestring
items[].technician.colorstring
items[].userNamestring or null
items[].minutesintegerWhole minutes (a running entry counts to now).
items[].datestringThe day it started, in the requested timezone.
totalinteger

Add a time entry by hand

POST /organizations/{organizationId}/time-entries

Records finished time with a reason (source MANUAL): for the caller, or (office roles with time.manage) for userId or technicianId (a technician linked to an account). It may not overlap another entry of the same kind for that person (409), start in the future or run over 24 hours (400). Technicians may add only their own time, on their own jobs. Returns the entry.

Permission: time.manage (Owner, Admin, Dispatcher, Technician (own time)); risk write (OAuth scope servflo.write).

Permissiontime.manage
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeEntries_create (toolset team)

Request body

FieldTypeRequiredDescription
userIdstringNoWhose time (office roles). Up to 200 characters.
technicianIdstring or nullNoWhose time, by technician (must be linked).
jobIdstring or nullNoThe job worked on (JOB / TRAVEL).
kindenumYesSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
startedAtstring (date-time)YesAn instant as an ISO 8601 date-time with offset, e.g. 2026-10-05T08:30:00-05:00.
endedAtstring (date-time)YesAn instant as an ISO 8601 date-time with offset, e.g. 2026-10-05T08:30:00-05:00.
notestring or nullNoUp to 2000 characters.
reasonstringYesWhy the entry is added or changed; kept on the entry for the approver. Up to 1000 characters.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time-entries" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"SHIFT","startedAt":"2026-11-03T15:00:00Z","endedAt":"2026-11-03T15:00:00Z","reason":"string"}'

Response

201: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

Edit a time entry

PATCH /organizations/{organizationId}/time-entries/{entryId}

Changes the times, kind, job or note of an entry, with a reason kept on it. An APPROVED entry can only be changed by someone who approves timesheets (400 otherwise); any other change sends the entry back to OPEN for review. Same overlap rules as adding. Technicians: their own entries only (404 otherwise). Returns the entry.

Permission: time.manage (Owner, Admin, Dispatcher, Technician (own time)); risk write (OAuth scope servflo.write).

Permissiontime.manage
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimeEntries_update (toolset team)

Parameters

NameInTypeRequiredDescription
entryIdpathstringYes

Request body

FieldTypeRequiredDescription
kindenumNoSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
startedAtstring (date-time)NoAn instant as an ISO 8601 date-time with offset, e.g. 2026-10-05T08:30:00-05:00.
endedAtstring (date-time) or nullNonull keeps it running (only one per kind).
jobIdstring or nullNo
notestring or nullNoUp to 2000 characters.
reasonstringYesWhy the entry is added or changed; kept on the entry for the approver. Up to 1000 characters.

Example

curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time-entries/{entryId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"string"}'

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
userIdstring
technicianIdstring or null
jobIdstring or null
kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
startedAtstring (date-time)
endedAtstring (date-time) or null
notestring or null
sourceenumOne of CLOCK, MANUAL, AUTO.
statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
editReasonstring or null
editedByUserIdstring or null
approvedByUserIdstring or null
approvedAtstring (date-time) or null
createdAtstring (date-time)
updatedAtstring (date-time)
jobobject or null
job.idstring
job.identifierstring
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
userNamestring or null
minutesintegerWhole minutes (a running entry counts to now).
datestringThe day it started, in the requested timezone.

Delete a time entry

DELETE /organizations/{organizationId}/time-entries/{entryId}

Destructive: deletes the entry. An APPROVED entry needs timesheets.approve. Technicians: their own entries only.

Permission: time.manage (Owner, Admin, Dispatcher, Technician (own time)); risk sensitive (OAuth scope servflo.sensitive).

Permissiontime.manage
Risksensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP tooltimeEntries_delete (toolset team)
ApprovalOver MCP, this tool always waits for your approval in ServFlo. See Approvals.

Parameters

NameInTypeRequiredDescription
entryIdpathstringYes

Example

curl -X DELETE "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/time-entries/{entryId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
oktrue

Everyone's week

GET /organizations/{organizationId}/timesheets

One row per person with time in the week (and every linked technician): minutes per day and kind, paid minutes, the week's approval state and whether a clock is running. Technicians get only their own row. Read-only. Open a person with GET /timesheets/{userId}.

Permission: time.read (Owner, Admin, Dispatcher, Technician (own time)); risk read (OAuth scope servflo.read).

Permissiontime.read
Riskread, OAuth scope servflo.read. Read: reads only.
MCP tooltimesheets_list (toolset team)

Parameters

NameInTypeRequiredDescription
weekStartquerystringYesThe first day (Monday) of the week, YYYY-MM-DD.
timeZonequerystringNoIANA timezone days and weeks are counted in (e.g. America/Chicago). Defaults to the organization's timezone (Settings → General). Up to 100 characters.

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/timesheets?weekStart=2026-11-03" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields

An array of objects:

FieldTypeDescription
userIdstring
userNamestring or null
technicianobject or null
technician.idstring
technician.namestring
technician.colorstring
statusenum or nullApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entryCountinteger
runningboolean
runningSincestring (date-time) or nullWhen the running clock started (the running shift, else the earliest running entry); null when nothing runs.
daysarray of object
days[].SHIFTinteger
days[].JOBinteger
days[].TRAVELinteger
days[].BREAKinteger
days[].ACTIVITYinteger
days[].paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
days[].datestring
totalsobject
totals.SHIFTinteger
totals.JOBinteger
totals.TRAVELinteger
totals.BREAKinteger
totals.ACTIVITYinteger
totals.paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
byJobarray of object
byJob[].jobIdstring
byJob[].minutesinteger

One person's week

GET /organizations/{organizationId}/timesheets/{userId}

A person's entries for the week with totals per day, for the week and per job, and the week's approval state. userId from GET /timesheets (or your own id from GET /users/me). Technicians: their own only (404 otherwise). Read-only.

Permission: time.read (Owner, Admin, Dispatcher, Technician (own time)); risk read (OAuth scope servflo.read).

Permissiontime.read
Riskread, OAuth scope servflo.read. Read: reads only.
MCP tooltimesheets_get (toolset team)

Parameters

NameInTypeRequiredDescription
userIdpathstringYesUp to 200 characters.
weekStartquerystringYesThe first day (Monday) of the week, YYYY-MM-DD.
timeZonequerystringNoIANA timezone days and weeks are counted in (e.g. America/Chicago). Defaults to the organization's timezone (Settings → General). Up to 100 characters.

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/timesheets/{userId}?weekStart=2026-11-03" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
userIdstring
userNamestring or null
weekStartstring
timeZonestring
statusenum or nullThe week's state; null when empty. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entriesarray of object
entries[].idstring
entries[].organizationIdstring
entries[].userIdstring
entries[].technicianIdstring or null
entries[].jobIdstring or null
entries[].kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
entries[].activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
entries[].startedAtstring (date-time)
entries[].endedAtstring (date-time) or null
entries[].notestring or null
entries[].sourceenumOne of CLOCK, MANUAL, AUTO.
entries[].statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entries[].editReasonstring or null
entries[].editedByUserIdstring or null
entries[].approvedByUserIdstring or null
entries[].approvedAtstring (date-time) or null
entries[].createdAtstring (date-time)
entries[].updatedAtstring (date-time)
entries[].jobobject or null
entries[].job.idstring
entries[].job.identifierstring
entries[].technicianobject or null
entries[].technician.idstring
entries[].technician.namestring
entries[].technician.colorstring
entries[].userNamestring or null
entries[].minutesintegerWhole minutes (a running entry counts to now).
entries[].datestringThe day it started, in the requested timezone.
daysarray of object
days[].SHIFTinteger
days[].JOBinteger
days[].TRAVELinteger
days[].BREAKinteger
days[].ACTIVITYinteger
days[].paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
days[].datestring
totalsobject
totals.SHIFTinteger
totals.JOBinteger
totals.TRAVELinteger
totals.BREAKinteger
totals.ACTIVITYinteger
totals.paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
byJobarray of object
byJob[].jobIdstring
byJob[].minutesinteger
byJob[].identifierstring or null

Submit your week

POST /organizations/{organizationId}/timesheets/submit

Submits the caller's finished OPEN or REJECTED entries for the week for approval (SUBMITTED). Refused while a clock is running (409) or when there is nothing to submit (400). Emits timesheet.submitted. Returns the week.

Permissiontime.track.own (Owner, Admin, Dispatcher, Technician)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimesheets_submit (toolset team)

Request body

FieldTypeRequiredDescription
weekStartstringYesThe first day (Monday) of the week, YYYY-MM-DD.
timeZonestringNoIANA timezone days and weeks are counted in (e.g. America/Chicago). Defaults to the organization's timezone (Settings → General). Up to 100 characters.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/timesheets/submit" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"weekStart":"2026-11-03"}'

Response

200: the answer's fields
FieldTypeDescription
userIdstring
userNamestring or null
weekStartstring
timeZonestring
statusenum or nullThe week's state; null when empty. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entriesarray of object
entries[].idstring
entries[].organizationIdstring
entries[].userIdstring
entries[].technicianIdstring or null
entries[].jobIdstring or null
entries[].kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
entries[].activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
entries[].startedAtstring (date-time)
entries[].endedAtstring (date-time) or null
entries[].notestring or null
entries[].sourceenumOne of CLOCK, MANUAL, AUTO.
entries[].statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entries[].editReasonstring or null
entries[].editedByUserIdstring or null
entries[].approvedByUserIdstring or null
entries[].approvedAtstring (date-time) or null
entries[].createdAtstring (date-time)
entries[].updatedAtstring (date-time)
entries[].jobobject or null
entries[].job.idstring
entries[].job.identifierstring
entries[].technicianobject or null
entries[].technician.idstring
entries[].technician.namestring
entries[].technician.colorstring
entries[].userNamestring or null
entries[].minutesintegerWhole minutes (a running entry counts to now).
entries[].datestringThe day it started, in the requested timezone.
daysarray of object
days[].SHIFTinteger
days[].JOBinteger
days[].TRAVELinteger
days[].BREAKinteger
days[].ACTIVITYinteger
days[].paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
days[].datestring
totalsobject
totals.SHIFTinteger
totals.JOBinteger
totals.TRAVELinteger
totals.BREAKinteger
totals.ACTIVITYinteger
totals.paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
byJobarray of object
byJob[].jobIdstring
byJob[].minutesinteger
byJob[].identifierstring or null

Approve or reject a person's week

POST /organizations/{organizationId}/timesheets/{userId}/review

decision: APPROVED approves every finished entry of the person's week (recording who and when); REJECTED sends them back to be fixed and resubmitted. Approved entries can then only be edited by approvers. Emits timesheet.approved / timesheet.rejected. Returns the week.

Permissiontimesheets.approve (Owner, Admin)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tooltimesheets_review (toolset team)

Parameters

NameInTypeRequiredDescription
userIdpathstringYesUp to 200 characters.

Request body

FieldTypeRequiredDescription
weekStartstringYesThe first day (Monday) of the week, YYYY-MM-DD.
timeZonestringNoIANA timezone days and weeks are counted in (e.g. America/Chicago). Defaults to the organization's timezone (Settings → General). Up to 100 characters.
decisionenumYesOne of APPROVED, REJECTED.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/timesheets/{userId}/review" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"weekStart":"2026-11-03","decision":"APPROVED"}'

Response

200: the answer's fields
FieldTypeDescription
userIdstring
userNamestring or null
weekStartstring
timeZonestring
statusenum or nullThe week's state; null when empty. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entriesarray of object
entries[].idstring
entries[].organizationIdstring
entries[].userIdstring
entries[].technicianIdstring or null
entries[].jobIdstring or null
entries[].kindenumSHIFT (clocked in), JOB (working a job), TRAVEL (on the way), BREAK, ACTIVITY (non-job work: see activity). One of SHIFT, JOB, TRAVEL, BREAK, ACTIVITY.
entries[].activityenum or nullWhat an ACTIVITY entry was spent on. One of SHOP, SUPPLY_HOUSE, MEETING, TRAINING, VEHICLE, ADMIN, OTHER.
entries[].startedAtstring (date-time)
entries[].endedAtstring (date-time) or null
entries[].notestring or null
entries[].sourceenumOne of CLOCK, MANUAL, AUTO.
entries[].statusenumApproval state: OPEN, SUBMITTED, APPROVED or REJECTED. Running = endedAt null. One of OPEN, SUBMITTED, APPROVED, REJECTED.
entries[].editReasonstring or null
entries[].editedByUserIdstring or null
entries[].approvedByUserIdstring or null
entries[].approvedAtstring (date-time) or null
entries[].createdAtstring (date-time)
entries[].updatedAtstring (date-time)
entries[].jobobject or null
entries[].job.idstring
entries[].job.identifierstring
entries[].technicianobject or null
entries[].technician.idstring
entries[].technician.namestring
entries[].technician.colorstring
entries[].userNamestring or null
entries[].minutesintegerWhole minutes (a running entry counts to now).
entries[].datestringThe day it started, in the requested timezone.
daysarray of object
days[].SHIFTinteger
days[].JOBinteger
days[].TRAVELinteger
days[].BREAKinteger
days[].ACTIVITYinteger
days[].paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
days[].datestring
totalsobject
totals.SHIFTinteger
totals.JOBinteger
totals.TRAVELinteger
totals.BREAKinteger
totals.ACTIVITYinteger
totals.paidintegerPaid minutes: SHIFT minus BREAK on a day with a shift, else JOB plus TRAVEL plus ACTIVITY.
byJobarray of object
byJob[].jobIdstring
byJob[].minutesinteger
byJob[].identifierstring or null

Export a week as CSV

GET /organizations/{organizationId}/timesheets/export

The week's entries as CSV (person, date, kind, start, end, decimal hours, job, status, source, note), for payroll. Optionally one person (userId). Technicians: their own only. Read-only.

Permission: time.read (Owner, Admin, Dispatcher, Technician (own time)); risk read (OAuth scope servflo.read).

Permissiontime.read
Riskread, OAuth scope servflo.read. Read: reads only.
MCP tooltimesheets_export (toolset team)

Parameters

NameInTypeRequiredDescription
weekStartquerystringYesThe first day (Monday) of the week, YYYY-MM-DD.
timeZonequerystringNoIANA timezone days and weeks are counted in (e.g. America/Chicago). Defaults to the organization's timezone (Settings → General). Up to 100 characters.
userIdquerystringNoUp to 200 characters.

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/timesheets/export?weekStart=2026-11-03" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
fileNamestring
contentTypestring
csvstring

On this page