@serviceai/api-spec 1.2.417 → 1.2.419

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/openapi.json +791 -41
  2. package/openapi.yaml +720 -39
  3. package/package.json +1 -1
  4. package/types.d.ts +382 -33
package/openapi.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: ServiceAi API
4
- version: 1.2.417
4
+ version: 1.2.419
5
5
  description: >-
6
6
  Source-of-truth API contract for ServiceAi. Generated from `shared/schema.ts` Zod schemas by
7
7
  `scripts/build-api-spec.ts`. Versioning policy: see `docs/api-versioning.md`. The hand-written docs/ios-contract.md
@@ -41,6 +41,8 @@ tags:
41
41
  description: >-
42
42
  When a person is expected to work (employee_shifts), and time-off requests against it. Separate from the job
43
43
  calendar by design.
44
+ - name: account
45
+ description: 'The signed-in person''s own account: asking for it to be deleted.'
44
46
  components:
45
47
  schemas:
46
48
  ErrorEnvelope:
@@ -87,7 +89,7 @@ components:
87
89
  properties:
88
90
  apiVersion:
89
91
  type: string
90
- example: 1.2.417
92
+ example: 1.2.419
91
93
  minClientVersion:
92
94
  type: string
93
95
  example: 1.0.0
@@ -2091,6 +2093,71 @@ components:
2091
2093
  minLength: 1
2092
2094
  required:
2093
2095
  - sdp
2096
+ PunchTimeCheck:
2097
+ type: object
2098
+ properties:
2099
+ reasons:
2100
+ type: array
2101
+ items:
2102
+ type: string
2103
+ enum:
2104
+ - server_time
2105
+ - unverified
2106
+ - clock_changed
2107
+ - too_old
2108
+ - replaced_auto_close
2109
+ - after_auto_close
2110
+ - auto_close_kept
2111
+ description: >-
2112
+ server_time: the phone's time was more than 2 minutes from the server's, and the server's was recorded.
2113
+ unverified: the phone restarted before sending, so the time couldn't be checked; the phone's was kept.
2114
+ clock_changed: the phone's clock was changed by hand. Late stops (T6): too_old: a stop more than 12 hours
2115
+ old that couldn't be proven was recorded at the 12-hour limit. replaced_auto_close: a proven stop replaced
2116
+ the automatic close (autoAt). after_auto_close: a stop later than the automatic close; the automatic close
2117
+ was kept. auto_close_kept: a stop earlier than the automatic close that wasn't applied (keptBecause).
2118
+ phoneAt:
2119
+ type:
2120
+ - string
2121
+ - 'null'
2122
+ format: date-time
2123
+ description: The phone's own time for the tap.
2124
+ serverAt:
2125
+ type:
2126
+ - string
2127
+ - 'null'
2128
+ format: date-time
2129
+ description: The tap by the server's clock (arrival minus tapElapsedMs); null when the phone couldn't say.
2130
+ receivedAt:
2131
+ type: string
2132
+ format: date-time
2133
+ description: When the request reached the server.
2134
+ tapElapsedMs:
2135
+ type:
2136
+ - integer
2137
+ - 'null'
2138
+ verified:
2139
+ type: boolean
2140
+ description: True when the phone's stopwatch proved the tap's time.
2141
+ autoAt:
2142
+ type: string
2143
+ format: date-time
2144
+ description: The automatic close a late stop replaced or was held against.
2145
+ keptBecause:
2146
+ type: string
2147
+ enum:
2148
+ - unverified
2149
+ - office
2150
+ - locked
2151
+ description: >-
2152
+ Why a late stop wasn't applied: its time couldn't be proven, the office had already changed or approved the
2153
+ time, or the pay period is locked.
2154
+ required:
2155
+ - reasons
2156
+ - phoneAt
2157
+ - serverAt
2158
+ - receivedAt
2159
+ - tapElapsedMs
2160
+ description: A tap whose time the office is asked to look at, with both times. It waits in Team › Needs attention.
2094
2161
  TimeEntry:
2095
2162
  type: object
2096
2163
  properties:
@@ -2189,6 +2256,46 @@ components:
2189
2256
  breakMinutes:
2190
2257
  type: integer
2191
2258
  description: Unpaid break minutes recorded against this entry.
2259
+ startCheck:
2260
+ type:
2261
+ - string
2262
+ - 'null'
2263
+ enum:
2264
+ - away
2265
+ - no_location
2266
+ description: >-
2267
+ Job time started by hand away from the job ('away') or with no usable location ('no_location'); null for a
2268
+ normal start. The office decides it.
2269
+ startDistanceM:
2270
+ type:
2271
+ - integer
2272
+ - 'null'
2273
+ description: How far from the job's site center the start was, when startCheck is 'away'.
2274
+ startReview:
2275
+ type:
2276
+ - string
2277
+ - 'null'
2278
+ enum:
2279
+ - approved
2280
+ - not_approved
2281
+ - moved
2282
+ description: >-
2283
+ The office's decision on a flagged start; null while it waits. 'not_approved' stays on the timesheet but off
2284
+ the job's costing.
2285
+ clockInCheck:
2286
+ allOf:
2287
+ - $ref: '#/components/schemas/PunchTimeCheck'
2288
+ - type:
2289
+ - object
2290
+ - 'null'
2291
+ description: Set when the start's time was checked and the office is asked to look; clockIn holds the time recorded.
2292
+ clockOutCheck:
2293
+ allOf:
2294
+ - $ref: '#/components/schemas/PunchTimeCheck'
2295
+ - type:
2296
+ - object
2297
+ - 'null'
2298
+ description: The same for the stop; clockOut holds the time recorded.
2192
2299
  createdAt:
2193
2300
  type:
2194
2301
  - string
@@ -2199,7 +2306,7 @@ components:
2199
2306
  description: Enrichment on /active and /entries. 'Office' for an office entry.
2200
2307
  userName:
2201
2308
  type: string
2202
- description: Enrichment on /entries and /active?scope=team.
2309
+ description: Enrichment on /entries.
2203
2310
  required:
2204
2311
  - id
2205
2312
  - organizationId
@@ -2356,6 +2463,22 @@ components:
2356
2463
  type:
2357
2464
  - string
2358
2465
  - 'null'
2466
+ clockInCheck:
2467
+ allOf:
2468
+ - $ref: '#/components/schemas/PunchTimeCheck'
2469
+ - type:
2470
+ - object
2471
+ - 'null'
2472
+ description: >-
2473
+ Set when the Clock In tap's time was checked and the office is asked to look; clockIn holds the time
2474
+ recorded.
2475
+ clockOutCheck:
2476
+ allOf:
2477
+ - $ref: '#/components/schemas/PunchTimeCheck'
2478
+ - type:
2479
+ - object
2480
+ - 'null'
2481
+ description: The same for the Clock Out tap; clockOut holds the time recorded.
2359
2482
  editedBy:
2360
2483
  type:
2361
2484
  - string
@@ -2451,6 +2574,28 @@ components:
2451
2574
  MyDayResponse:
2452
2575
  type: object
2453
2576
  properties:
2577
+ payrollAddon:
2578
+ type: boolean
2579
+ description: >-
2580
+ The company's time tracking (payroll) add-on. False: no Clock In/Out, no work days, schedules, time cards or
2581
+ pay; job time still runs. Then open is null, today is empty, shiftEnd is null, payroll.breakButton and
2582
+ locationTrail are false, and the payroll routes answer 403 PAYROLL_OFF. Not the same as payroll.enabled.
2583
+ payroll:
2584
+ type: object
2585
+ properties:
2586
+ enabled:
2587
+ type: boolean
2588
+ description: A pay period is set (the company's pay rules are on).
2589
+ breakButton:
2590
+ type: boolean
2591
+ description: Show the break button (false when this person's lunch comes off automatically).
2592
+ breaksPaid:
2593
+ type: boolean
2594
+ required:
2595
+ - enabled
2596
+ - breakButton
2597
+ - breaksPaid
2598
+ description: The company's pay rules as they touch this person.
2454
2599
  dayModel:
2455
2600
  type: boolean
2456
2601
  description: True once this person is on the day model.
@@ -2507,6 +2652,8 @@ components:
2507
2652
  - address
2508
2653
  description: The office fence, when the org profile address is on the map.
2509
2654
  required:
2655
+ - payrollAddon
2656
+ - payroll
2510
2657
  - dayModel
2511
2658
  - open
2512
2659
  - today
@@ -2648,11 +2795,40 @@ components:
2648
2795
  autoClockEnabled:
2649
2796
  type: boolean
2650
2797
  description: The user's auto-clock toggle at crossing time. False → the crossing is logged but never acted on.
2798
+ tapElapsedMs:
2799
+ type:
2800
+ - integer
2801
+ - 'null'
2802
+ minimum: 0
2803
+ description: >-
2804
+ Milliseconds from the tap to this send, from the phone's monotonic clock (a stopwatch the person can't
2805
+ change). An offline replay sends the time since the tap. The server's tap time is its own clock when the
2806
+ request arrives minus this. Within 2 minutes of the phone's time, the phone's time stands (never later than
2807
+ the tap); further apart, the server's tap time is recorded and the office is asked to look. A Clock Out or
2808
+ job time stop it proves this way keeps its real time however old; without it, a stop older than 12 hours is
2809
+ recorded at the 12-hour limit and flagged (T6). Send it with every queued tap, clock-outs included.
2810
+ tapTimeUnverified:
2811
+ type:
2812
+ - boolean
2813
+ - 'null'
2814
+ description: >-
2815
+ True when the phone restarted between the tap and this send, so there is no stopwatch reading (send no
2816
+ tapElapsedMs). A tap more than 2 minutes old keeps the phone's time and is flagged for the office.
2817
+ deviceClockChanged:
2818
+ type:
2819
+ - boolean
2820
+ - 'null'
2821
+ description: >-
2822
+ True when the phone's clock was changed by hand since the phone last synced. The tap is flagged for the
2823
+ office.
2651
2824
  required:
2652
2825
  - clientEventId
2653
2826
  - kind
2654
2827
  - occurredAt
2655
- description: One fence crossing as the device saw it.
2828
+ description: >-
2829
+ One fence crossing as the device saw it. With tapElapsedMs (the time since the crossing), the server checks
2830
+ occurredAt against its own clock before the resolver uses it, and the entry the crossing opens or closes carries
2831
+ the check. A departure it proves isn't held to the 12-hour limit (T6).
2656
2832
  CrossingsAck:
2657
2833
  type: object
2658
2834
  properties:
@@ -2802,6 +2978,12 @@ components:
2802
2978
  canEdit:
2803
2979
  type: boolean
2804
2980
  description: True only for the primary account.
2981
+ jobSiteTracking:
2982
+ type: boolean
2983
+ description: >-
2984
+ The caller's own switch, set by the office (owner's O9). False: watch no job-site fences and use no location
2985
+ for job time; job time comes only from Start/Stop job time. The office fence, Clock In/Out and the company
2986
+ switches are unaffected. Absent (an older server): treat as true.
2805
2987
  required:
2806
2988
  - serverResolver
2807
2989
  - dwellSeconds
@@ -2987,6 +3169,11 @@ components:
2987
3169
  type: string
2988
3170
  format: date
2989
3171
  description: The org-local date, which is not necessarily the device's.
3172
+ payrollAddon:
3173
+ type: boolean
3174
+ description: >-
3175
+ False when the company has the time tracking add-on off: no shifts, hours or time off come back, and nothing
3176
+ covers now.
2990
3177
  hours:
2991
3178
  type: object
2992
3179
  additionalProperties:
@@ -3069,12 +3256,73 @@ components:
3069
3256
  - from
3070
3257
  - to
3071
3258
  - today
3259
+ - payrollAddon
3072
3260
  - hours
3073
3261
  - attendance
3074
3262
  - shifts
3075
3263
  - timeOff
3076
3264
  - recentTimeOff
3077
3265
  description: When I should be in, plus what I actually worked. The clock-in expectation lives here.
3266
+ OnTheClockRow:
3267
+ type: object
3268
+ properties:
3269
+ userId:
3270
+ type: string
3271
+ userName:
3272
+ type: string
3273
+ entryId:
3274
+ type:
3275
+ - integer
3276
+ - 'null'
3277
+ description: The open site entry; null between sites (a day with nothing open).
3278
+ jobId:
3279
+ type:
3280
+ - integer
3281
+ - 'null'
3282
+ siteType:
3283
+ type: string
3284
+ enum:
3285
+ - job
3286
+ - office
3287
+ - none
3288
+ description: '''none'' between sites.'
3289
+ jobName:
3290
+ type:
3291
+ - string
3292
+ - 'null'
3293
+ since:
3294
+ type: string
3295
+ format: date-time
3296
+ description: When the work day began, else when the entry did.
3297
+ required:
3298
+ - userId
3299
+ - userName
3300
+ - entryId
3301
+ - jobId
3302
+ - siteType
3303
+ - jobName
3304
+ - since
3305
+ description: >-
3306
+ One person on the clock (server/onTheClock.ts). Job time with no paid day behind it is left out: that person is
3307
+ at a job, not on the clock.
3308
+ AccountDeletionRequest:
3309
+ type: object
3310
+ properties:
3311
+ status:
3312
+ type: string
3313
+ enum:
3314
+ - pending
3315
+ - completed
3316
+ - cancelled
3317
+ requestedAt:
3318
+ type: string
3319
+ format: date-time
3320
+ required:
3321
+ - status
3322
+ - requestedAt
3323
+ description: >-
3324
+ The signed-in person's request to have their account deleted. pending: waiting for the company; completed: the
3325
+ company marked it done; cancelled: the company cancelled it (asking again starts a new one).
3078
3326
  parameters: {}
3079
3327
  paths:
3080
3328
  /api/version:
@@ -4272,13 +4520,33 @@ paths:
4272
4520
  placementSource:
4273
4521
  type: string
4274
4522
  enum: *ref_1
4523
+ duplicateOf:
4524
+ type: object
4525
+ properties:
4526
+ roomId:
4527
+ type: integer
4528
+ description: Id of the room whose copy of this doorway is drawn.
4529
+ doorIndex:
4530
+ type: integer
4531
+ minimum: 0
4532
+ description: Index into that room's `doors[]`.
4533
+ required:
4534
+ - roomId
4535
+ - doorIndex
4536
+ description: >-
4537
+ Present only on the later-scanned room's copy of a doorway two rooms in this response
4538
+ share (the rooms are linked by a door link, or their doors coincide on one wall). Skip
4539
+ this door's glyph and keep its wall gap; the door it names is the one to draw. Absent
4540
+ on every other door. Read-time only: never send it back, it is never stored.
4275
4541
  required:
4276
4542
  - position
4277
4543
  - width
4278
4544
  - height
4279
4545
  description: >-
4280
4546
  RoomPlan-classified doors. Distinct from `windows[]` and `openings[]`; append-room
4281
- `entryDoorwayId` tokens of the form `…-door-<idx>` resolve into THIS array only.
4547
+ `entryDoorwayId` tokens of the form `…-door-<idx>` resolve into THIS array only. A door
4548
+ carrying `duplicateOf` is the hidden copy of a shared doorway (one door per doorway, from
4549
+ the room scanned first).
4282
4550
  windows:
4283
4551
  type: array
4284
4552
  items:
@@ -4513,13 +4781,34 @@ paths:
4513
4781
  placementSource:
4514
4782
  type: string
4515
4783
  enum: *ref_1
4784
+ duplicateOf:
4785
+ type: object
4786
+ properties:
4787
+ roomId:
4788
+ type: integer
4789
+ description: Id of the room whose copy of this doorway is drawn.
4790
+ doorIndex:
4791
+ type: integer
4792
+ minimum: 0
4793
+ description: Index into that room's `doors[]`.
4794
+ required:
4795
+ - roomId
4796
+ - doorIndex
4797
+ description: >-
4798
+ Present only on the later-scanned room's copy of a doorway two rooms in this
4799
+ response share (the rooms are linked by a door link, or their doors coincide on
4800
+ one wall). Skip this door's glyph and keep its wall gap; the door it names is the
4801
+ one to draw. Absent on every other door. Read-time only: never send it back, it is
4802
+ never stored.
4516
4803
  required:
4517
4804
  - position
4518
4805
  - width
4519
4806
  - height
4520
4807
  description: >-
4521
4808
  RoomPlan-classified doors. Distinct from `windows[]` and `openings[]`; append-room
4522
- `entryDoorwayId` tokens of the form `…-door-<idx>` resolve into THIS array only.
4809
+ `entryDoorwayId` tokens of the form `…-door-<idx>` resolve into THIS array only. A door
4810
+ carrying `duplicateOf` is the hidden copy of a shared doorway (one door per doorway,
4811
+ from the room scanned first).
4523
4812
  windows:
4524
4813
  type: array
4525
4814
  items:
@@ -5203,7 +5492,37 @@ paths:
5203
5492
  - string
5204
5493
  - 'null'
5205
5494
  format: date-time
5206
- description: When the tap happened. The server clamps it.
5495
+ description: >-
5496
+ When the tap happened, by the phone's clock. The server checks it against its own (tapElapsedMs) and
5497
+ clamps it. From the web (device 'web') it is ignored: the server's receipt time is used.
5498
+ tapElapsedMs:
5499
+ type:
5500
+ - integer
5501
+ - 'null'
5502
+ minimum: 0
5503
+ description: >-
5504
+ Milliseconds from the tap to this send, from the phone's monotonic clock (a stopwatch the person
5505
+ can't change). An offline replay sends the time since the tap. The server's tap time is its own
5506
+ clock when the request arrives minus this. Within 2 minutes of the phone's time, the phone's time
5507
+ stands (never later than the tap); further apart, the server's tap time is recorded and the office
5508
+ is asked to look. A Clock Out or job time stop it proves this way keeps its real time however old;
5509
+ without it, a stop older than 12 hours is recorded at the 12-hour limit and flagged (T6). Send it
5510
+ with every queued tap, clock-outs included.
5511
+ tapTimeUnverified:
5512
+ type:
5513
+ - boolean
5514
+ - 'null'
5515
+ description: >-
5516
+ True when the phone restarted between the tap and this send, so there is no stopwatch reading (send
5517
+ no tapElapsedMs). A tap more than 2 minutes old keeps the phone's time and is flagged for the
5518
+ office.
5519
+ deviceClockChanged:
5520
+ type:
5521
+ - boolean
5522
+ - 'null'
5523
+ description: >-
5524
+ True when the phone's clock was changed by hand since the phone last synced. The tap is flagged for
5525
+ the office.
5207
5526
  notes:
5208
5527
  type:
5209
5528
  - string
@@ -5229,7 +5548,10 @@ paths:
5229
5548
  schema:
5230
5549
  $ref: '#/components/schemas/ErrorEnvelope'
5231
5550
  '403':
5232
- description: Time tracking access denied, or no organization on the session.
5551
+ description: >-
5552
+ Time tracking access denied, no organization on the session, or code PAYROLL_OFF: the company has the time
5553
+ tracking add-on off (GET /api/time-tracking/day says payrollAddon: false). Don't retry; hide Clock In/Out
5554
+ and the other payroll controls.
5233
5555
  content:
5234
5556
  application/json:
5235
5557
  schema:
@@ -5265,12 +5587,19 @@ paths:
5265
5587
  $ref: '#/components/schemas/UpgradeRequiredEnvelope'
5266
5588
  /api/time-tracking/day/clock-out:
5267
5589
  post:
5268
- summary: End my day. Closes every open entry with it.
5590
+ summary: >-
5591
+ End my day: the paid day only. Job time (fence or job timer) runs until it is stopped or the phone reports
5592
+ leaving, and is never paid past Clock Out.
5269
5593
  tags:
5270
5594
  - time-tracking
5271
5595
  parameters:
5272
5596
  - *ref_3
5273
5597
  - *ref_4
5598
+ description: >-
5599
+ A late Clock Out (T6): with tapElapsedMs proving its time, it keeps its real time however old. When the safety
5600
+ net already closed the day (the automatic close), a proven Clock Out earlier than that close replaces it
5601
+ (replacedAutoClose, flagged for the office); anything else about that day is flagged for the office and changes
5602
+ nothing. A proven Clock Out from before the running day started never ends the running day.
5274
5603
  requestBody:
5275
5604
  content:
5276
5605
  application/json:
@@ -5304,7 +5633,37 @@ paths:
5304
5633
  - string
5305
5634
  - 'null'
5306
5635
  format: date-time
5307
- description: When the tap happened. The server clamps it.
5636
+ description: >-
5637
+ When the tap happened, by the phone's clock. The server checks it against its own (tapElapsedMs) and
5638
+ clamps it. From the web (device 'web') it is ignored: the server's receipt time is used.
5639
+ tapElapsedMs:
5640
+ type:
5641
+ - integer
5642
+ - 'null'
5643
+ minimum: 0
5644
+ description: >-
5645
+ Milliseconds from the tap to this send, from the phone's monotonic clock (a stopwatch the person
5646
+ can't change). An offline replay sends the time since the tap. The server's tap time is its own
5647
+ clock when the request arrives minus this. Within 2 minutes of the phone's time, the phone's time
5648
+ stands (never later than the tap); further apart, the server's tap time is recorded and the office
5649
+ is asked to look. A Clock Out or job time stop it proves this way keeps its real time however old;
5650
+ without it, a stop older than 12 hours is recorded at the 12-hour limit and flagged (T6). Send it
5651
+ with every queued tap, clock-outs included.
5652
+ tapTimeUnverified:
5653
+ type:
5654
+ - boolean
5655
+ - 'null'
5656
+ description: >-
5657
+ True when the phone restarted between the tap and this send, so there is no stopwatch reading (send
5658
+ no tapElapsedMs). A tap more than 2 minutes old keeps the phone's time and is flagged for the
5659
+ office.
5660
+ deviceClockChanged:
5661
+ type:
5662
+ - boolean
5663
+ - 'null'
5664
+ description: >-
5665
+ True when the phone's clock was changed by hand since the phone last synced. The tap is flagged for
5666
+ the office.
5308
5667
  note:
5309
5668
  type:
5310
5669
  - string
@@ -5312,7 +5671,7 @@ paths:
5312
5671
  maxLength: 2000
5313
5672
  responses:
5314
5673
  '200':
5315
- description: Day ended.
5674
+ description: Day ended, or the automatic close of the day this tap belongs to replaced (replacedAutoClose).
5316
5675
  content:
5317
5676
  application/json:
5318
5677
  schema:
@@ -5324,11 +5683,10 @@ paths:
5324
5683
  - type:
5325
5684
  - object
5326
5685
  - 'null'
5327
- closedEntries:
5328
- type: integer
5686
+ replacedAutoClose:
5687
+ type: boolean
5329
5688
  required:
5330
5689
  - day
5331
- - closedEntries
5332
5690
  '401':
5333
5691
  description: Not signed in.
5334
5692
  content:
@@ -5336,19 +5694,24 @@ paths:
5336
5694
  schema:
5337
5695
  $ref: '#/components/schemas/ErrorEnvelope'
5338
5696
  '403':
5339
- description: Time tracking access denied, or no organization on the session.
5697
+ description: >-
5698
+ Time tracking access denied, no organization on the session, or code PAYROLL_OFF: the company has the time
5699
+ tracking add-on off (GET /api/time-tracking/day says payrollAddon: false). Don't retry; hide Clock In/Out
5700
+ and the other payroll controls.
5340
5701
  content:
5341
5702
  application/json:
5342
5703
  schema:
5343
5704
  $ref: '#/components/schemas/ErrorEnvelope'
5344
5705
  '404':
5345
- description: No day is running (code NO_OPEN_DAY).
5706
+ description: No day is running (code NO_OPEN_DAY). Also when the tap was only flagged for the office.
5346
5707
  content:
5347
5708
  application/json:
5348
5709
  schema:
5349
5710
  $ref: '#/components/schemas/ErrorEnvelope'
5350
5711
  '409':
5351
- description: The day was already closed.
5712
+ description: >-
5713
+ The day was already closed; or code DAY_CLOSED_AUTOMATICALLY: the tap belongs to an earlier day the safety
5714
+ net closed, and was flagged for the office.
5352
5715
  content:
5353
5716
  application/json:
5354
5717
  schema:
@@ -5507,7 +5870,9 @@ paths:
5507
5870
  description: Prepend the office fence (jobId null). Only send it if this client can decode a null jobId.
5508
5871
  responses:
5509
5872
  '200':
5510
- description: Fences, office first when requested.
5873
+ description: >-
5874
+ Fences, office first when requested. No job fences for someone the office turned job-site tracking off for
5875
+ (clock-config jobSiteTracking false), except with includeClosed for the manual picker.
5511
5876
  content:
5512
5877
  application/json:
5513
5878
  schema:
@@ -5534,7 +5899,7 @@ paths:
5534
5899
  $ref: '#/components/schemas/UpgradeRequiredEnvelope'
5535
5900
  /api/time-tracking/active:
5536
5901
  get:
5537
- summary: My open entries right now (or the team's).
5902
+ summary: My open entries right now, or who is on the clock.
5538
5903
  tags:
5539
5904
  - time-tracking
5540
5905
  parameters:
@@ -5547,16 +5912,23 @@ paths:
5547
5912
  type: string
5548
5913
  enum:
5549
5914
  - team
5550
- description: Everyone on the clock. Requires time_tracking.view_all.
5915
+ - me
5916
+ description: >-
5917
+ `team`: everyone on the clock, one OnTheClockRow per person; requires time_tracking.view_all. `me`: the
5918
+ caller's own row while on the clock. Without scope: the caller's open entries.
5551
5919
  responses:
5552
5920
  '200':
5553
- description: Open entries. Normally zero or one for a single user.
5921
+ description: 'Without scope: open entries, normally zero or one. With scope: one OnTheClockRow per person on the clock.'
5554
5922
  content:
5555
5923
  application/json:
5556
5924
  schema:
5557
- type: array
5558
- items:
5559
- $ref: '#/components/schemas/TimeEntry'
5925
+ anyOf:
5926
+ - type: array
5927
+ items:
5928
+ $ref: '#/components/schemas/TimeEntry'
5929
+ - type: array
5930
+ items:
5931
+ $ref: '#/components/schemas/OnTheClockRow'
5560
5932
  '401':
5561
5933
  description: Not signed in.
5562
5934
  content:
@@ -5647,10 +6019,15 @@ paths:
5647
6019
  $ref: '#/components/schemas/UpgradeRequiredEnvelope'
5648
6020
  /api/time-tracking/clock-in:
5649
6021
  post:
5650
- summary: Clock in to a job or the office by hand.
6022
+ summary: Start time on a job or at the office by hand.
5651
6023
  description: >-
5652
- A deliberate button press. On the day model a press with no day running starts the day at that moment; a
5653
- fence-driven clock-in from an old-model client cannot, and is refused.
6024
+ A deliberate button press. The entry joins the person's work day when one is running. With none running: a job
6025
+ timer press (startDay: false) records job time only and never starts the paid day; a Clock In press (startDay
6026
+ omitted: older iPhones' job picker, the office's "Clock X in") starts the day at that moment; an automatic
6027
+ clock-in is refused with NO_OPEN_DAY, because the fence crossing already records the arrival. A person starting
6028
+ their own job time away from the job's site, or with no usable location, is asked first (send canConfirmAway:
6029
+ 409 JOB_TIME_AWAY carries the question; resend with confirmAway once they say yes) and the entry is flagged
6030
+ (startCheck) for the office to approve, move or not count. Older apps start straight away, flagged the same.
5654
6031
  tags:
5655
6032
  - time-tracking
5656
6033
  parameters:
@@ -5674,6 +6051,10 @@ paths:
5674
6051
  userId:
5675
6052
  type: string
5676
6053
  description: Clock someone else in. Requires time_tracking.edit_all.
6054
+ note:
6055
+ type: string
6056
+ maxLength: 2000
6057
+ description: Why, when clocking someone else in. Required with userId.
5677
6058
  lat:
5678
6059
  type: number
5679
6060
  lng:
@@ -5683,6 +6064,11 @@ paths:
5683
6064
  enum:
5684
6065
  - manual
5685
6066
  - auto
6067
+ startDay:
6068
+ type: boolean
6069
+ description: >-
6070
+ false: a job timer press, job time only; it never starts the paid day. Omitted: a Clock In press
6071
+ starts the day when none is running.
5686
6072
  notes:
5687
6073
  type: string
5688
6074
  budgetItemId:
@@ -5692,7 +6078,10 @@ paths:
5692
6078
  occurredAt:
5693
6079
  type: string
5694
6080
  format: date-time
5695
- description: When the tech actually arrived. The server clamps it.
6081
+ description: >-
6082
+ When the tech actually arrived, by the phone's clock. The server checks it against its own
6083
+ (tapElapsedMs) and clamps it. From the web (device 'web') it is ignored: the server's receipt time
6084
+ is used.
5696
6085
  accuracyM:
5697
6086
  type:
5698
6087
  - number
@@ -5703,6 +6092,42 @@ paths:
5703
6092
  - string
5704
6093
  - 'null'
5705
6094
  enum: *ref_5
6095
+ canConfirmAway:
6096
+ type: boolean
6097
+ description: >-
6098
+ This app can ask the person before starting job time away from the job (it handles 409
6099
+ JOB_TIME_AWAY).
6100
+ confirmAway:
6101
+ type: boolean
6102
+ description: The person said yes to starting away from the job.
6103
+ tapElapsedMs:
6104
+ type:
6105
+ - integer
6106
+ - 'null'
6107
+ minimum: 0
6108
+ description: >-
6109
+ Milliseconds from the tap to this send, from the phone's monotonic clock (a stopwatch the person
6110
+ can't change). An offline replay sends the time since the tap. The server's tap time is its own
6111
+ clock when the request arrives minus this. Within 2 minutes of the phone's time, the phone's time
6112
+ stands (never later than the tap); further apart, the server's tap time is recorded and the office
6113
+ is asked to look. A Clock Out or job time stop it proves this way keeps its real time however old;
6114
+ without it, a stop older than 12 hours is recorded at the 12-hour limit and flagged (T6). Send it
6115
+ with every queued tap, clock-outs included.
6116
+ tapTimeUnverified:
6117
+ type:
6118
+ - boolean
6119
+ - 'null'
6120
+ description: >-
6121
+ True when the phone restarted between the tap and this send, so there is no stopwatch reading (send
6122
+ no tapElapsedMs). A tap more than 2 minutes old keeps the phone's time and is flagged for the
6123
+ office.
6124
+ deviceClockChanged:
6125
+ type:
6126
+ - boolean
6127
+ - 'null'
6128
+ description: >-
6129
+ True when the phone's clock was changed by hand since the phone last synced. The tap is flagged for
6130
+ the office.
5706
6131
  responses:
5707
6132
  '201':
5708
6133
  description: Entry opened.
@@ -5711,7 +6136,7 @@ paths:
5711
6136
  schema:
5712
6137
  $ref: '#/components/schemas/TimeEntry'
5713
6138
  '400':
5714
- description: Validation failure, or jobId missing on a job clock-in.
6139
+ description: Validation failure, jobId missing on a job clock-in, or no note when clocking someone else in.
5715
6140
  content:
5716
6141
  application/json:
5717
6142
  schema:
@@ -5723,7 +6148,10 @@ paths:
5723
6148
  schema:
5724
6149
  $ref: '#/components/schemas/ErrorEnvelope'
5725
6150
  '403':
5726
- description: Time tracking access denied, or no organization on the session.
6151
+ description: >-
6152
+ Time tracking access denied, or no organization on the session. PAYROLL_OFF: a Clock In press that would
6153
+ start the day at a company with the time tracking add-on off. JOB_TIME_OFF: the company has both the time
6154
+ tracking add-on and location tracking off, so job time isn't used; stopping running job time still works.
5727
6155
  content:
5728
6156
  application/json:
5729
6157
  schema:
@@ -5736,8 +6164,13 @@ paths:
5736
6164
  $ref: '#/components/schemas/ErrorEnvelope'
5737
6165
  '409':
5738
6166
  description: >-
5739
- Already clocked in at this site (code ALREADY_CLOCKED_IN_SAME_JOB). The body carries the existing entry —
5740
- adopt it.
6167
+ ALREADY_CLOCKED_IN_SAME_JOB: already at this site; the body carries the existing entry — adopt it.
6168
+ ALREADY_CLOCKED_IN_OTHER_JOB: an automatic clock-in while another entry is open; the body carries it.
6169
+ NO_OPEN_DAY: an automatic clock-in with no work day running; the fence crossing records the arrival.
6170
+ JOB_SITE_TRACKING_OFF: an automatic clock-in for someone the office turned job-site tracking off for; start
6171
+ job time by hand. PERIOD_LOCKED: an admin clock-in inside a locked pay period. JOB_TIME_AWAY: starting job
6172
+ time away from the job (reason 'away', with distanceM) or with no usable location (reason 'no_location');
6173
+ show message as the question and resend with confirmAway: true.
5741
6174
  content:
5742
6175
  application/json:
5743
6176
  schema:
@@ -5749,10 +6182,22 @@ paths:
5749
6182
  type: string
5750
6183
  entry:
5751
6184
  $ref: '#/components/schemas/TimeEntry'
6185
+ reason:
6186
+ type: string
6187
+ enum:
6188
+ - away
6189
+ - no_location
6190
+ distanceM:
6191
+ type:
6192
+ - integer
6193
+ - 'null'
6194
+ jobName:
6195
+ type:
6196
+ - string
6197
+ - 'null'
5752
6198
  required:
5753
6199
  - message
5754
6200
  - code
5755
- - entry
5756
6201
  '426':
5757
6202
  description: Client major below minClientVersion.
5758
6203
  content:
@@ -5798,7 +6243,10 @@ paths:
5798
6243
  occurredAt:
5799
6244
  type: string
5800
6245
  format: date-time
5801
- description: When the tech actually left. Clamped to [entry.clockIn, now].
6246
+ description: >-
6247
+ When the tech actually left, by the phone's clock. Checked against the server's (tapElapsedMs) and
6248
+ clamped to [entry.clockIn, now], and to the last 12 hours unless tapElapsedMs proves it (T6). From
6249
+ the web (device 'web') it is ignored: the server's receipt time is used.
5802
6250
  accuracyM:
5803
6251
  type:
5804
6252
  - number
@@ -5809,9 +6257,39 @@ paths:
5809
6257
  - string
5810
6258
  - 'null'
5811
6259
  enum: *ref_5
6260
+ tapElapsedMs:
6261
+ type:
6262
+ - integer
6263
+ - 'null'
6264
+ minimum: 0
6265
+ description: >-
6266
+ Milliseconds from the tap to this send, from the phone's monotonic clock (a stopwatch the person
6267
+ can't change). An offline replay sends the time since the tap. The server's tap time is its own
6268
+ clock when the request arrives minus this. Within 2 minutes of the phone's time, the phone's time
6269
+ stands (never later than the tap); further apart, the server's tap time is recorded and the office
6270
+ is asked to look. A Clock Out or job time stop it proves this way keeps its real time however old;
6271
+ without it, a stop older than 12 hours is recorded at the 12-hour limit and flagged (T6). Send it
6272
+ with every queued tap, clock-outs included.
6273
+ tapTimeUnverified:
6274
+ type:
6275
+ - boolean
6276
+ - 'null'
6277
+ description: >-
6278
+ True when the phone restarted between the tap and this send, so there is no stopwatch reading (send
6279
+ no tapElapsedMs). A tap more than 2 minutes old keeps the phone's time and is flagged for the
6280
+ office.
6281
+ deviceClockChanged:
6282
+ type:
6283
+ - boolean
6284
+ - 'null'
6285
+ description: >-
6286
+ True when the phone's clock was changed by hand since the phone last synced. The tap is flagged for
6287
+ the office.
5812
6288
  responses:
5813
6289
  '200':
5814
- description: Entry closed.
6290
+ description: >-
6291
+ Entry closed; or, for a stop tapElapsedMs proves that is earlier than the automatic close of the job time
6292
+ there, that close replaced (T6).
5815
6293
  content:
5816
6294
  application/json:
5817
6295
  schema:
@@ -5835,7 +6313,9 @@ paths:
5835
6313
  schema:
5836
6314
  $ref: '#/components/schemas/ErrorEnvelope'
5837
6315
  '404':
5838
- description: No open entry at that site.
6316
+ description: >-
6317
+ No open entry at that site. Also when the stop belongs to job time the safety net closed and was only
6318
+ flagged for the office.
5839
6319
  content:
5840
6320
  application/json:
5841
6321
  schema:
@@ -5851,7 +6331,13 @@ paths:
5851
6331
  summary: Move my clock to another job in one atomic call.
5852
6332
  description: >-
5853
6333
  Closes the caller's open entry and opens one on the target. One request instead of clock-out + clock-in, so an
5854
- offline drop between the two cannot strand the tech clocked out. Own entry only.
6334
+ offline drop between the two cannot strand the tech clocked out. Own entry only. Moving job time to a job the
6335
+ person isn't at, or with no usable location, is asked first (send canConfirmAway: 409 JOB_TIME_AWAY carries the
6336
+ question; resend with confirmAway once they say yes) and the new entry is flagged (startCheck) for the office.
6337
+ Older apps move straight away, flagged the same. switchAt is a time the person picked, so earlier than the tap
6338
+ is normal. With tapElapsedMs it is never later than the tap by the server's clock, and more than 2 minutes later
6339
+ is replaced by the tap time and flagged (opened.clockInCheck). tapTimeUnverified doesn't apply;
6340
+ deviceClockChanged flags it.
5855
6341
  tags:
5856
6342
  - time-tracking
5857
6343
  parameters:
@@ -5874,6 +6360,47 @@ paths:
5874
6360
  type: number
5875
6361
  lng:
5876
6362
  type: number
6363
+ accuracyM:
6364
+ type:
6365
+ - number
6366
+ - 'null'
6367
+ minimum: 0
6368
+ canConfirmAway:
6369
+ type: boolean
6370
+ description: >-
6371
+ This app can ask the person before moving job time to a job they aren't at (it handles 409
6372
+ JOB_TIME_AWAY).
6373
+ confirmAway:
6374
+ type: boolean
6375
+ description: The person said yes to moving it anyway.
6376
+ tapElapsedMs:
6377
+ type:
6378
+ - integer
6379
+ - 'null'
6380
+ minimum: 0
6381
+ description: >-
6382
+ Milliseconds from the tap to this send, from the phone's monotonic clock (a stopwatch the person
6383
+ can't change). An offline replay sends the time since the tap. The server's tap time is its own
6384
+ clock when the request arrives minus this. Within 2 minutes of the phone's time, the phone's time
6385
+ stands (never later than the tap); further apart, the server's tap time is recorded and the office
6386
+ is asked to look. A Clock Out or job time stop it proves this way keeps its real time however old;
6387
+ without it, a stop older than 12 hours is recorded at the 12-hour limit and flagged (T6). Send it
6388
+ with every queued tap, clock-outs included.
6389
+ tapTimeUnverified:
6390
+ type:
6391
+ - boolean
6392
+ - 'null'
6393
+ description: >-
6394
+ True when the phone restarted between the tap and this send, so there is no stopwatch reading (send
6395
+ no tapElapsedMs). A tap more than 2 minutes old keeps the phone's time and is flagged for the
6396
+ office.
6397
+ deviceClockChanged:
6398
+ type:
6399
+ - boolean
6400
+ - 'null'
6401
+ description: >-
6402
+ True when the phone's clock was changed by hand since the phone last synced. The tap is flagged for
6403
+ the office.
5877
6404
  required:
5878
6405
  - targetJobId
5879
6406
  responses:
@@ -5907,7 +6434,9 @@ paths:
5907
6434
  schema:
5908
6435
  $ref: '#/components/schemas/ErrorEnvelope'
5909
6436
  '403':
5910
- description: Time tracking access denied, or no organization on the session.
6437
+ description: >-
6438
+ Time tracking access denied, or no organization on the session. JOB_TIME_OFF: the company has both the time
6439
+ tracking add-on and location tracking off, so job time isn't used; stopping running job time still works.
5911
6440
  content:
5912
6441
  application/json:
5913
6442
  schema:
@@ -5918,6 +6447,36 @@ paths:
5918
6447
  application/json:
5919
6448
  schema:
5920
6449
  $ref: '#/components/schemas/ErrorEnvelope'
6450
+ '409':
6451
+ description: >-
6452
+ PERIOD_LOCKED: the switch time is inside a locked pay period. JOB_TIME_AWAY: the person isn't at the target
6453
+ job (reason 'away', with distanceM) or has no usable location (reason 'no_location'); nothing moved. Show
6454
+ message as the question and resend with confirmAway: true.
6455
+ content:
6456
+ application/json:
6457
+ schema:
6458
+ type: object
6459
+ properties:
6460
+ message:
6461
+ type: string
6462
+ code:
6463
+ type: string
6464
+ reason:
6465
+ type: string
6466
+ enum:
6467
+ - away
6468
+ - no_location
6469
+ distanceM:
6470
+ type:
6471
+ - integer
6472
+ - 'null'
6473
+ jobName:
6474
+ type:
6475
+ - string
6476
+ - 'null'
6477
+ required:
6478
+ - message
6479
+ - code
5921
6480
  '426':
5922
6481
  description: Client major below minClientVersion.
5923
6482
  content:
@@ -6174,7 +6733,10 @@ paths:
6174
6733
  schema:
6175
6734
  $ref: '#/components/schemas/ErrorEnvelope'
6176
6735
  '403':
6177
- description: Time tracking access denied, or no organization on the session.
6736
+ description: >-
6737
+ Time tracking access denied, no organization on the session, or code PAYROLL_OFF: the company has the time
6738
+ tracking add-on off (GET /api/time-tracking/day says payrollAddon: false). Don't retry; hide Clock In/Out
6739
+ and the other payroll controls.
6178
6740
  content:
6179
6741
  application/json:
6180
6742
  schema:
@@ -6342,6 +6904,15 @@ paths:
6342
6904
  application/json:
6343
6905
  schema:
6344
6906
  $ref: '#/components/schemas/ErrorEnvelope'
6907
+ '403':
6908
+ description: >-
6909
+ Time tracking access denied, no organization on the session, or code PAYROLL_OFF: the company has the time
6910
+ tracking add-on off (GET /api/time-tracking/day says payrollAddon: false). Don't retry; hide Clock In/Out
6911
+ and the other payroll controls.
6912
+ content:
6913
+ application/json:
6914
+ schema:
6915
+ $ref: '#/components/schemas/ErrorEnvelope'
6345
6916
  '409':
6346
6917
  description: A request for those dates is already waiting.
6347
6918
  content:
@@ -6354,4 +6925,114 @@ paths:
6354
6925
  application/json:
6355
6926
  schema:
6356
6927
  $ref: '#/components/schemas/UpgradeRequiredEnvelope'
6928
+ /api/account/deletion-request:
6929
+ post:
6930
+ summary: Ask for my account to be deleted.
6931
+ description: >-
6932
+ Nothing is deleted by this call. The request goes to the company's owner and Team admins (bell, push and email)
6933
+ and to ServiceAi support (email); the company removes the account. Asking again while a request is pending
6934
+ answers that request with 200 and creates nothing; everyone is reminded at most once a day. The reason is never
6935
+ put in a push.
6936
+ tags:
6937
+ - account
6938
+ parameters:
6939
+ - *ref_3
6940
+ requestBody:
6941
+ content:
6942
+ application/json:
6943
+ schema:
6944
+ type: object
6945
+ properties:
6946
+ reason:
6947
+ type:
6948
+ - string
6949
+ - 'null'
6950
+ maxLength: 1000
6951
+ description: Optional. Trimmed; at most 1000 characters after trimming.
6952
+ responses:
6953
+ '200':
6954
+ description: 'A request was already pending: this is that request.'
6955
+ content:
6956
+ application/json:
6957
+ schema:
6958
+ $ref: '#/components/schemas/AccountDeletionRequest'
6959
+ '202':
6960
+ description: Request created.
6961
+ content:
6962
+ application/json:
6963
+ schema:
6964
+ $ref: '#/components/schemas/AccountDeletionRequest'
6965
+ '400':
6966
+ description: Validation failure (a reason longer than 1000 characters).
6967
+ content:
6968
+ application/json:
6969
+ schema:
6970
+ $ref: '#/components/schemas/ErrorEnvelope'
6971
+ '401':
6972
+ description: Not signed in.
6973
+ content:
6974
+ application/json:
6975
+ schema:
6976
+ $ref: '#/components/schemas/ErrorEnvelope'
6977
+ '403':
6978
+ description: No organization on the session, or viewing the app as someone else.
6979
+ content:
6980
+ application/json:
6981
+ schema:
6982
+ $ref: '#/components/schemas/ErrorEnvelope'
6983
+ '426':
6984
+ description: Client major below minClientVersion.
6985
+ content:
6986
+ application/json:
6987
+ schema:
6988
+ $ref: '#/components/schemas/UpgradeRequiredEnvelope'
6989
+ '429':
6990
+ description: Too many requests from this person; try again later.
6991
+ content:
6992
+ application/json:
6993
+ schema:
6994
+ $ref: '#/components/schemas/ErrorEnvelope'
6995
+ get:
6996
+ summary: My newest account deletion request.
6997
+ tags:
6998
+ - account
6999
+ parameters:
7000
+ - *ref_3
7001
+ responses:
7002
+ '200':
7003
+ description: The newest request, whatever its status.
7004
+ content:
7005
+ application/json:
7006
+ schema:
7007
+ $ref: '#/components/schemas/AccountDeletionRequest'
7008
+ '401':
7009
+ description: Not signed in.
7010
+ content:
7011
+ application/json:
7012
+ schema:
7013
+ $ref: '#/components/schemas/ErrorEnvelope'
7014
+ '403':
7015
+ description: No organization on the session.
7016
+ content:
7017
+ application/json:
7018
+ schema:
7019
+ $ref: '#/components/schemas/ErrorEnvelope'
7020
+ '404':
7021
+ description: No request yet.
7022
+ content:
7023
+ application/json:
7024
+ schema:
7025
+ type: object
7026
+ properties:
7027
+ message:
7028
+ type: string
7029
+ example: No deletion request
7030
+ required:
7031
+ - message
7032
+ '426':
7033
+ description: Client major below minClientVersion.
7034
+ content:
7035
+ application/json:
7036
+ schema:
7037
+ $ref: '#/components/schemas/UpgradeRequiredEnvelope'
6357
7038
  webhooks: {}