@serviceai/api-spec 1.1.24 → 1.2.1

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.
package/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "ServiceAi API",
5
- "version": "1.1.24",
5
+ "version": "1.2.1",
6
6
  "description": "Source-of-truth API contract for ServiceAi. Generated from `shared/schema.ts` Zod schemas by `scripts/build-api-spec.ts`. Versioning policy: see `docs/api-versioning.md`. The hand-written docs/ios-contract.md and docs/lidar-mobile-upload-contract.md are human-readable design notes only — this spec is the canonical wire contract.",
7
7
  "license": {
8
8
  "name": "Proprietary",
@@ -114,7 +114,7 @@
114
114
  "properties": {
115
115
  "apiVersion": {
116
116
  "type": "string",
117
- "example": "1.1.24"
117
+ "example": "1.2.1"
118
118
  },
119
119
  "minClientVersion": {
120
120
  "type": "string",
@@ -1942,9 +1942,70 @@
1942
1942
  "category": {
1943
1943
  "type": "string"
1944
1944
  },
1945
+ "roomLocalId": {
1946
+ "type": "string",
1947
+ "maxLength": 128
1948
+ },
1945
1949
  "walls": {
1946
1950
  "type": "array",
1947
- "items": {}
1951
+ "items": {
1952
+ "type": "object",
1953
+ "properties": {
1954
+ "start": {
1955
+ "type": "object",
1956
+ "properties": {
1957
+ "x": {
1958
+ "type": "number"
1959
+ },
1960
+ "y": {
1961
+ "type": "number"
1962
+ }
1963
+ },
1964
+ "required": [
1965
+ "x",
1966
+ "y"
1967
+ ]
1968
+ },
1969
+ "end": {
1970
+ "type": "object",
1971
+ "properties": {
1972
+ "x": {
1973
+ "type": "number"
1974
+ },
1975
+ "y": {
1976
+ "type": "number"
1977
+ }
1978
+ },
1979
+ "required": [
1980
+ "x",
1981
+ "y"
1982
+ ]
1983
+ },
1984
+ "length": {
1985
+ "type": "number"
1986
+ },
1987
+ "height": {
1988
+ "type": "number"
1989
+ },
1990
+ "captureMetadata": {
1991
+ "type": "object",
1992
+ "properties": {
1993
+ "identifier": {
1994
+ "type": "string"
1995
+ },
1996
+ "isDoor": {
1997
+ "type": "boolean"
1998
+ },
1999
+ "isOpening": {
2000
+ "type": "boolean"
2001
+ }
2002
+ }
2003
+ },
2004
+ "captureMethod": {
2005
+ "type": "string"
2006
+ }
2007
+ }
2008
+ }
1948
2009
  },
1949
2010
  "doors": {
1950
2011
  "type": "array",
@@ -1959,15 +2020,11 @@
1959
2020
  },
1960
2021
  "y": {
1961
2022
  "type": "number"
1962
- },
1963
- "z": {
1964
- "type": "number"
1965
2023
  }
1966
2024
  },
1967
2025
  "required": [
1968
2026
  "x",
1969
- "y",
1970
- "z"
2027
+ "y"
1971
2028
  ]
1972
2029
  },
1973
2030
  "width": {
@@ -2025,15 +2082,11 @@
2025
2082
  },
2026
2083
  "y": {
2027
2084
  "type": "number"
2028
- },
2029
- "z": {
2030
- "type": "number"
2031
2085
  }
2032
2086
  },
2033
2087
  "required": [
2034
2088
  "x",
2035
- "y",
2036
- "z"
2089
+ "y"
2037
2090
  ]
2038
2091
  },
2039
2092
  "width": {
@@ -2091,15 +2144,11 @@
2091
2144
  },
2092
2145
  "y": {
2093
2146
  "type": "number"
2094
- },
2095
- "z": {
2096
- "type": "number"
2097
2147
  }
2098
2148
  },
2099
2149
  "required": [
2100
2150
  "x",
2101
- "y",
2102
- "z"
2151
+ "y"
2103
2152
  ]
2104
2153
  },
2105
2154
  "width": {
@@ -2143,6 +2192,119 @@
2143
2192
  "height"
2144
2193
  ]
2145
2194
  }
2195
+ },
2196
+ "pins": {
2197
+ "type": "array",
2198
+ "items": {
2199
+ "type": "object",
2200
+ "properties": {
2201
+ "id": {
2202
+ "type": "string",
2203
+ "minLength": 1,
2204
+ "maxLength": 128
2205
+ },
2206
+ "position": {
2207
+ "type": "object",
2208
+ "properties": {
2209
+ "x": {
2210
+ "type": "number"
2211
+ },
2212
+ "y": {
2213
+ "type": "number"
2214
+ }
2215
+ },
2216
+ "required": [
2217
+ "x",
2218
+ "y"
2219
+ ]
2220
+ },
2221
+ "label": {
2222
+ "type": "string",
2223
+ "maxLength": 256
2224
+ },
2225
+ "roomLocalId": {
2226
+ "type": "string",
2227
+ "maxLength": 128
2228
+ },
2229
+ "kind": {
2230
+ "type": "string",
2231
+ "maxLength": 64
2232
+ },
2233
+ "note": {
2234
+ "type": "string",
2235
+ "maxLength": 1024
2236
+ }
2237
+ },
2238
+ "required": [
2239
+ "id",
2240
+ "position"
2241
+ ]
2242
+ }
2243
+ },
2244
+ "objects": {
2245
+ "type": "array",
2246
+ "items": {
2247
+ "type": "object",
2248
+ "properties": {
2249
+ "category": {
2250
+ "type": "string"
2251
+ },
2252
+ "position": {
2253
+ "type": "object",
2254
+ "properties": {
2255
+ "x": {
2256
+ "type": "number"
2257
+ },
2258
+ "y": {
2259
+ "type": "number"
2260
+ }
2261
+ },
2262
+ "required": [
2263
+ "x",
2264
+ "y"
2265
+ ]
2266
+ },
2267
+ "dimensions": {
2268
+ "type": "object",
2269
+ "properties": {
2270
+ "width": {
2271
+ "type": "number"
2272
+ },
2273
+ "height": {
2274
+ "type": "number"
2275
+ },
2276
+ "depth": {
2277
+ "type": "number"
2278
+ }
2279
+ }
2280
+ }
2281
+ }
2282
+ }
2283
+ },
2284
+ "boundaryPolygon": {
2285
+ "type": "array",
2286
+ "items": {
2287
+ "type": "object",
2288
+ "properties": {
2289
+ "x": {
2290
+ "type": "number"
2291
+ },
2292
+ "y": {
2293
+ "type": "number"
2294
+ }
2295
+ },
2296
+ "required": [
2297
+ "x",
2298
+ "y"
2299
+ ]
2300
+ }
2301
+ },
2302
+ "roomNote": {
2303
+ "type": "string",
2304
+ "maxLength": 4096
2305
+ },
2306
+ "entryDoorwayId": {
2307
+ "type": "string"
2146
2308
  }
2147
2309
  },
2148
2310
  "description": "Mobile-upload scan room. `doors`, `windows`, and `openings` are three distinct non-overlapping arrays — array membership IS the discriminator (there is no `kind` / `type` field on the item itself). Append-room `entryDoorwayId` of the form `scan-<scanId>-room-<roomLocalId>-(door|opening)-<idx>` resolves kind-specifically against the parent scan's matching array only: a `door` segment indexes `doors[]`, an `opening` segment indexes `openings[]`. Windows are not traversable and never appear as an `entryDoorwayId` target."
@@ -2151,7 +2313,8 @@
2151
2313
  },
2152
2314
  "required": [
2153
2315
  "rooms"
2154
- ]
2316
+ ],
2317
+ "description": "RoomPlan JSON envelope. `rooms[]` carries the per-room geometry (walls/doors/windows/openings/pins/objects/boundaryPolygon). iOS continues to upload positions in the RoomPlan-native frame; the server applies the view-alignment intake transform once at `/scans/mobile-upload` (see docs/lidar-contract-v4.md)."
2155
2318
  },
2156
2319
  "name": {
2157
2320
  "type": "string",
@@ -2173,8 +2336,36 @@
2173
2336
  "type": "string"
2174
2337
  },
2175
2338
  "wallConfidences": {
2176
- "type": "array",
2177
- "items": {}
2339
+ "anyOf": [
2340
+ {
2341
+ "type": "array",
2342
+ "items": {
2343
+ "type": [
2344
+ "array",
2345
+ "null"
2346
+ ],
2347
+ "items": {
2348
+ "type": "number"
2349
+ }
2350
+ }
2351
+ },
2352
+ {
2353
+ "type": "array",
2354
+ "items": {
2355
+ "type": "number"
2356
+ }
2357
+ },
2358
+ {
2359
+ "type": "object",
2360
+ "additionalProperties": {
2361
+ "type": "array",
2362
+ "items": {
2363
+ "type": "number"
2364
+ }
2365
+ }
2366
+ }
2367
+ ],
2368
+ "description": "Per-room per-wall capture confidences. Canonical shape: nested `(number[] | null)[]` parallel to `roomPlanJSON.rooms[]`. Two legacy shapes are still accepted for backwards compatibility but should not be emitted by new clients: a flat `number[]` (treated as room[0] confidences) and an object keyed by `roomLocalId` (or numeric index as string). See `normalizeWallConfidences` in `server/routes/lidar.ts`."
2178
2369
  },
2179
2370
  "deviceTrack": {},
2180
2371
  "referencePhotos": {
@@ -2286,13 +2477,40 @@
2286
2477
  },
2287
2478
  "clientMirrorApplied": {
2288
2479
  "type": "boolean"
2480
+ },
2481
+ "parentScanId": {
2482
+ "type": "integer",
2483
+ "exclusiveMinimum": 0
2484
+ },
2485
+ "entryDoorwayId": {
2486
+ "type": "string",
2487
+ "description": "Append-room doorway link to a target room on the parent composite. Format: `scan-<scanId>-room-<roomLocalId>-(door|opening)-<idx>`. The trailing segment selects kind-specifically — `door` indexes the target room's `doors[]`, `opening` indexes `openings[]`; the two are never folded. `<scanId>` and `<roomLocalId>` must reference a scan + room on the parent composite the iOS client is appending to."
2488
+ },
2489
+ "entryDoorwayWallIndex": {
2490
+ "type": "integer",
2491
+ "minimum": 0
2492
+ },
2493
+ "entryDoorwayPosition": {
2494
+ "type": "object",
2495
+ "properties": {
2496
+ "x": {
2497
+ "type": "number"
2498
+ },
2499
+ "y": {
2500
+ "type": "number"
2501
+ }
2502
+ },
2503
+ "required": [
2504
+ "x",
2505
+ "y"
2506
+ ]
2289
2507
  }
2290
2508
  },
2291
2509
  "required": [
2292
2510
  "roomPlanJSON",
2293
2511
  "name"
2294
2512
  ],
2295
- "description": "POST /api/jobs/:jobId/scans/mobile-upload body. Required: `roomPlanJSON` (object with nested `rooms` array) + `name`. All other fields are additive / optional per the v2 contract."
2513
+ "description": "POST /api/jobs/:jobId/scans/mobile-upload body. v5 of the LiDAR contract (Task #879) — additive over v3/v4. Required: `roomPlanJSON` (object with nested `rooms` array) + `name`. All v3 and v4 payloads remain accepted byte-for-byte. New first-class fields: per-room `roomLocalId`, `pins[]`, `objects[]`, `boundaryPolygon[]`, per-wall `captureMetadata`; append-room `parentScanId`, `entryDoorwayId`, `entryDoorwayWallIndex`, `entryDoorwayPosition`; opening `position` is 2D `{x, y}` (an additional `z` is tolerated for backwards compatibility but ignored by the server). `wallConfidences` advertises the canonical nested `(number[] | null)[]` shape; the two legacy shapes (flat `number[]` and `Record<roomLocalIdOrIndex, number[]>`) are still parsed by `normalizeWallConfidences` in `server/routes/lidar.ts`."
2296
2514
  },
2297
2515
  "MobileUploadResponse": {
2298
2516
  "type": "object",
@@ -4759,15 +4977,11 @@
4759
4977
  },
4760
4978
  "y": {
4761
4979
  "type": "number"
4762
- },
4763
- "z": {
4764
- "type": "number"
4765
4980
  }
4766
4981
  },
4767
4982
  "required": [
4768
4983
  "x",
4769
- "y",
4770
- "z"
4984
+ "y"
4771
4985
  ]
4772
4986
  },
4773
4987
  "width": {
@@ -4826,15 +5040,11 @@
4826
5040
  },
4827
5041
  "y": {
4828
5042
  "type": "number"
4829
- },
4830
- "z": {
4831
- "type": "number"
4832
5043
  }
4833
5044
  },
4834
5045
  "required": [
4835
5046
  "x",
4836
- "y",
4837
- "z"
5047
+ "y"
4838
5048
  ]
4839
5049
  },
4840
5050
  "width": {
@@ -4893,15 +5103,11 @@
4893
5103
  },
4894
5104
  "y": {
4895
5105
  "type": "number"
4896
- },
4897
- "z": {
4898
- "type": "number"
4899
5106
  }
4900
5107
  },
4901
5108
  "required": [
4902
5109
  "x",
4903
- "y",
4904
- "z"
5110
+ "y"
4905
5111
  ]
4906
5112
  },
4907
5113
  "width": {
@@ -5141,15 +5347,11 @@
5141
5347
  },
5142
5348
  "y": {
5143
5349
  "type": "number"
5144
- },
5145
- "z": {
5146
- "type": "number"
5147
5350
  }
5148
5351
  },
5149
5352
  "required": [
5150
5353
  "x",
5151
- "y",
5152
- "z"
5354
+ "y"
5153
5355
  ]
5154
5356
  },
5155
5357
  "width": {
@@ -5208,15 +5410,11 @@
5208
5410
  },
5209
5411
  "y": {
5210
5412
  "type": "number"
5211
- },
5212
- "z": {
5213
- "type": "number"
5214
5413
  }
5215
5414
  },
5216
5415
  "required": [
5217
5416
  "x",
5218
- "y",
5219
- "z"
5417
+ "y"
5220
5418
  ]
5221
5419
  },
5222
5420
  "width": {
@@ -5275,15 +5473,11 @@
5275
5473
  },
5276
5474
  "y": {
5277
5475
  "type": "number"
5278
- },
5279
- "z": {
5280
- "type": "number"
5281
5476
  }
5282
5477
  },
5283
5478
  "required": [
5284
5479
  "x",
5285
- "y",
5286
- "z"
5480
+ "y"
5287
5481
  ]
5288
5482
  },
5289
5483
  "width": {
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.1.24
4
+ version: 1.2.1
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
@@ -78,7 +78,7 @@ components:
78
78
  properties:
79
79
  apiVersion:
80
80
  type: string
81
- example: 1.1.24
81
+ example: 1.2.1
82
82
  minClientVersion:
83
83
  type: string
84
84
  example: 1.0.0
@@ -1234,9 +1234,49 @@ components:
1234
1234
  type: string
1235
1235
  category:
1236
1236
  type: string
1237
+ roomLocalId:
1238
+ type: string
1239
+ maxLength: 128
1237
1240
  walls:
1238
1241
  type: array
1239
- items: {}
1242
+ items:
1243
+ type: object
1244
+ properties:
1245
+ start:
1246
+ type: object
1247
+ properties:
1248
+ x:
1249
+ type: number
1250
+ 'y':
1251
+ type: number
1252
+ required:
1253
+ - x
1254
+ - 'y'
1255
+ end:
1256
+ type: object
1257
+ properties:
1258
+ x:
1259
+ type: number
1260
+ 'y':
1261
+ type: number
1262
+ required:
1263
+ - x
1264
+ - 'y'
1265
+ length:
1266
+ type: number
1267
+ height:
1268
+ type: number
1269
+ captureMetadata:
1270
+ type: object
1271
+ properties:
1272
+ identifier:
1273
+ type: string
1274
+ isDoor:
1275
+ type: boolean
1276
+ isOpening:
1277
+ type: boolean
1278
+ captureMethod:
1279
+ type: string
1240
1280
  doors:
1241
1281
  type: array
1242
1282
  items:
@@ -1249,12 +1289,9 @@ components:
1249
1289
  type: number
1250
1290
  'y':
1251
1291
  type: number
1252
- z:
1253
- type: number
1254
1292
  required:
1255
1293
  - x
1256
1294
  - 'y'
1257
- - z
1258
1295
  width:
1259
1296
  type: number
1260
1297
  exclusiveMinimum: 0
@@ -1296,12 +1333,9 @@ components:
1296
1333
  type: number
1297
1334
  'y':
1298
1335
  type: number
1299
- z:
1300
- type: number
1301
1336
  required:
1302
1337
  - x
1303
1338
  - 'y'
1304
- - z
1305
1339
  width:
1306
1340
  type: number
1307
1341
  exclusiveMinimum: 0
@@ -1338,12 +1372,9 @@ components:
1338
1372
  type: number
1339
1373
  'y':
1340
1374
  type: number
1341
- z:
1342
- type: number
1343
1375
  required:
1344
1376
  - x
1345
1377
  - 'y'
1346
- - z
1347
1378
  width:
1348
1379
  type: number
1349
1380
  exclusiveMinimum: 0
@@ -1368,6 +1399,83 @@ components:
1368
1399
  - position
1369
1400
  - width
1370
1401
  - height
1402
+ pins:
1403
+ type: array
1404
+ items:
1405
+ type: object
1406
+ properties:
1407
+ id:
1408
+ type: string
1409
+ minLength: 1
1410
+ maxLength: 128
1411
+ position:
1412
+ type: object
1413
+ properties:
1414
+ x:
1415
+ type: number
1416
+ 'y':
1417
+ type: number
1418
+ required:
1419
+ - x
1420
+ - 'y'
1421
+ label:
1422
+ type: string
1423
+ maxLength: 256
1424
+ roomLocalId:
1425
+ type: string
1426
+ maxLength: 128
1427
+ kind:
1428
+ type: string
1429
+ maxLength: 64
1430
+ note:
1431
+ type: string
1432
+ maxLength: 1024
1433
+ required:
1434
+ - id
1435
+ - position
1436
+ objects:
1437
+ type: array
1438
+ items:
1439
+ type: object
1440
+ properties:
1441
+ category:
1442
+ type: string
1443
+ position:
1444
+ type: object
1445
+ properties:
1446
+ x:
1447
+ type: number
1448
+ 'y':
1449
+ type: number
1450
+ required:
1451
+ - x
1452
+ - 'y'
1453
+ dimensions:
1454
+ type: object
1455
+ properties:
1456
+ width:
1457
+ type: number
1458
+ height:
1459
+ type: number
1460
+ depth:
1461
+ type: number
1462
+ boundaryPolygon:
1463
+ type: array
1464
+ items:
1465
+ type: object
1466
+ properties:
1467
+ x:
1468
+ type: number
1469
+ 'y':
1470
+ type: number
1471
+ required:
1472
+ - x
1473
+ - 'y'
1474
+ roomNote:
1475
+ type: string
1476
+ maxLength: 4096
1477
+ entryDoorwayId:
1478
+ type: string
1371
1479
  description: >-
1372
1480
  Mobile-upload scan room. `doors`, `windows`, and `openings` are three distinct non-overlapping arrays
1373
1481
  — array membership IS the discriminator (there is no `kind` / `type` field on the item itself).
@@ -1377,6 +1485,11 @@ components:
1377
1485
  an `entryDoorwayId` target.
1378
1486
  required:
1379
1487
  - rooms
1488
+ description: >-
1489
+ RoomPlan JSON envelope. `rooms[]` carries the per-room geometry
1490
+ (walls/doors/windows/openings/pins/objects/boundaryPolygon). iOS continues to upload positions in the
1491
+ RoomPlan-native frame; the server applies the view-alignment intake transform once at `/scans/mobile-upload`
1492
+ (see docs/lidar-contract-v4.md).
1380
1493
  name:
1381
1494
  type: string
1382
1495
  minLength: 1
@@ -1391,8 +1504,27 @@ components:
1391
1504
  userOrientationHint:
1392
1505
  type: string
1393
1506
  wallConfidences:
1394
- type: array
1395
- items: {}
1507
+ anyOf:
1508
+ - type: array
1509
+ items:
1510
+ type:
1511
+ - array
1512
+ - 'null'
1513
+ items:
1514
+ type: number
1515
+ - type: array
1516
+ items:
1517
+ type: number
1518
+ - type: object
1519
+ additionalProperties:
1520
+ type: array
1521
+ items:
1522
+ type: number
1523
+ description: >-
1524
+ Per-room per-wall capture confidences. Canonical shape: nested `(number[] | null)[]` parallel to
1525
+ `roomPlanJSON.rooms[]`. Two legacy shapes are still accepted for backwards compatibility but should not be
1526
+ emitted by new clients: a flat `number[]` (treated as room[0] confidences) and an object keyed by
1527
+ `roomLocalId` (or numeric index as string). See `normalizeWallConfidences` in `server/routes/lidar.ts`.
1396
1528
  deviceTrack: {}
1397
1529
  referencePhotos:
1398
1530
  type: array
@@ -1473,12 +1605,42 @@ components:
1473
1605
  type: string
1474
1606
  clientMirrorApplied:
1475
1607
  type: boolean
1608
+ parentScanId:
1609
+ type: integer
1610
+ exclusiveMinimum: 0
1611
+ entryDoorwayId:
1612
+ type: string
1613
+ description: >-
1614
+ Append-room doorway link to a target room on the parent composite. Format:
1615
+ `scan-<scanId>-room-<roomLocalId>-(door|opening)-<idx>`. The trailing segment selects kind-specifically —
1616
+ `door` indexes the target room's `doors[]`, `opening` indexes `openings[]`; the two are never folded.
1617
+ `<scanId>` and `<roomLocalId>` must reference a scan + room on the parent composite the iOS client is
1618
+ appending to.
1619
+ entryDoorwayWallIndex:
1620
+ type: integer
1621
+ minimum: 0
1622
+ entryDoorwayPosition:
1623
+ type: object
1624
+ properties:
1625
+ x:
1626
+ type: number
1627
+ 'y':
1628
+ type: number
1629
+ required:
1630
+ - x
1631
+ - 'y'
1476
1632
  required:
1477
1633
  - roomPlanJSON
1478
1634
  - name
1479
1635
  description: >-
1480
- POST /api/jobs/:jobId/scans/mobile-upload body. Required: `roomPlanJSON` (object with nested `rooms` array) +
1481
- `name`. All other fields are additive / optional per the v2 contract.
1636
+ POST /api/jobs/:jobId/scans/mobile-upload body. v5 of the LiDAR contract (Task #879) — additive over v3/v4.
1637
+ Required: `roomPlanJSON` (object with nested `rooms` array) + `name`. All v3 and v4 payloads remain accepted
1638
+ byte-for-byte. New first-class fields: per-room `roomLocalId`, `pins[]`, `objects[]`, `boundaryPolygon[]`,
1639
+ per-wall `captureMetadata`; append-room `parentScanId`, `entryDoorwayId`, `entryDoorwayWallIndex`,
1640
+ `entryDoorwayPosition`; opening `position` is 2D `{x, y}` (an additional `z` is tolerated for backwards
1641
+ compatibility but ignored by the server). `wallConfidences` advertises the canonical nested `(number[] |
1642
+ null)[]` shape; the two legacy shapes (flat `number[]` and `Record<roomLocalIdOrIndex, number[]>`) are still
1643
+ parsed by `normalizeWallConfidences` in `server/routes/lidar.ts`.
1482
1644
  MobileUploadResponse:
1483
1645
  type: object
1484
1646
  properties:
@@ -2994,12 +3156,9 @@ paths:
2994
3156
  type: number
2995
3157
  'y':
2996
3158
  type: number
2997
- z:
2998
- type: number
2999
3159
  required:
3000
3160
  - x
3001
3161
  - 'y'
3002
- - z
3003
3162
  width:
3004
3163
  type: number
3005
3164
  exclusiveMinimum: 0
@@ -3039,12 +3198,9 @@ paths:
3039
3198
  type: number
3040
3199
  'y':
3041
3200
  type: number
3042
- z:
3043
- type: number
3044
3201
  required:
3045
3202
  - x
3046
3203
  - 'y'
3047
- - z
3048
3204
  width:
3049
3205
  type: number
3050
3206
  exclusiveMinimum: 0
@@ -3084,12 +3240,9 @@ paths:
3084
3240
  type: number
3085
3241
  'y':
3086
3242
  type: number
3087
- z:
3088
- type: number
3089
3243
  required:
3090
3244
  - x
3091
3245
  - 'y'
3092
- - z
3093
3246
  width:
3094
3247
  type: number
3095
3248
  exclusiveMinimum: 0
@@ -3244,12 +3397,9 @@ paths:
3244
3397
  type: number
3245
3398
  'y':
3246
3399
  type: number
3247
- z:
3248
- type: number
3249
3400
  required:
3250
3401
  - x
3251
3402
  - 'y'
3252
- - z
3253
3403
  width:
3254
3404
  type: number
3255
3405
  exclusiveMinimum: 0
@@ -3289,12 +3439,9 @@ paths:
3289
3439
  type: number
3290
3440
  'y':
3291
3441
  type: number
3292
- z:
3293
- type: number
3294
3442
  required:
3295
3443
  - x
3296
3444
  - 'y'
3297
- - z
3298
3445
  width:
3299
3446
  type: number
3300
3447
  exclusiveMinimum: 0
@@ -3334,12 +3481,9 @@ paths:
3334
3481
  type: number
3335
3482
  'y':
3336
3483
  type: number
3337
- z:
3338
- type: number
3339
3484
  required:
3340
3485
  - x
3341
3486
  - 'y'
3342
- - z
3343
3487
  width:
3344
3488
  type: number
3345
3489
  exclusiveMinimum: 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serviceai/api-spec",
3
- "version": "1.1.24",
3
+ "version": "1.2.1",
4
4
  "description": "ServiceAi OpenAPI 3.1 contract + generated TS types. Auto-published from the desktop repo.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/types.d.ts CHANGED
@@ -1064,7 +1064,6 @@ export interface paths {
1064
1064
  position: {
1065
1065
  x: number;
1066
1066
  y: number;
1067
- z: number;
1068
1067
  };
1069
1068
  width: number;
1070
1069
  height: number;
@@ -1080,7 +1079,6 @@ export interface paths {
1080
1079
  position: {
1081
1080
  x: number;
1082
1081
  y: number;
1083
- z: number;
1084
1082
  };
1085
1083
  width: number;
1086
1084
  height: number;
@@ -1096,7 +1094,6 @@ export interface paths {
1096
1094
  position: {
1097
1095
  x: number;
1098
1096
  y: number;
1099
- z: number;
1100
1097
  };
1101
1098
  width: number;
1102
1099
  height: number;
@@ -1198,7 +1195,6 @@ export interface paths {
1198
1195
  position: {
1199
1196
  x: number;
1200
1197
  y: number;
1201
- z: number;
1202
1198
  };
1203
1199
  width: number;
1204
1200
  height: number;
@@ -1214,7 +1210,6 @@ export interface paths {
1214
1210
  position: {
1215
1211
  x: number;
1216
1212
  y: number;
1217
- z: number;
1218
1213
  };
1219
1214
  width: number;
1220
1215
  height: number;
@@ -1230,7 +1225,6 @@ export interface paths {
1230
1225
  position: {
1231
1226
  x: number;
1232
1227
  y: number;
1233
- z: number;
1234
1228
  };
1235
1229
  width: number;
1236
1230
  height: number;
@@ -1848,7 +1842,7 @@ export interface components {
1848
1842
  };
1849
1843
  /** @description Cross-platform version handshake manifest. See docs/api-versioning.md. */
1850
1844
  VersionManifest: {
1851
- /** @example 1.1.24 */
1845
+ /** @example 1.2.1 */
1852
1846
  apiVersion: string;
1853
1847
  /** @example 1.0.0 */
1854
1848
  minClientVersion: string;
@@ -2140,19 +2134,37 @@ export interface components {
2140
2134
  orgId: number;
2141
2135
  role: string;
2142
2136
  };
2143
- /** @description POST /api/jobs/:jobId/scans/mobile-upload body. Required: `roomPlanJSON` (object with nested `rooms` array) + `name`. All other fields are additive / optional per the v2 contract. */
2137
+ /** @description POST /api/jobs/:jobId/scans/mobile-upload body. v5 of the LiDAR contract (Task #879) — additive over v3/v4. Required: `roomPlanJSON` (object with nested `rooms` array) + `name`. All v3 and v4 payloads remain accepted byte-for-byte. New first-class fields: per-room `roomLocalId`, `pins[]`, `objects[]`, `boundaryPolygon[]`, per-wall `captureMetadata`; append-room `parentScanId`, `entryDoorwayId`, `entryDoorwayWallIndex`, `entryDoorwayPosition`; opening `position` is 2D `{x, y}` (an additional `z` is tolerated for backwards compatibility but ignored by the server). `wallConfidences` advertises the canonical nested `(number[] | null)[]` shape; the two legacy shapes (flat `number[]` and `Record<roomLocalIdOrIndex, number[]>`) are still parsed by `normalizeWallConfidences` in `server/routes/lidar.ts`. */
2144
2138
  MobileUploadRequest: {
2139
+ /** @description RoomPlan JSON envelope. `rooms[]` carries the per-room geometry (walls/doors/windows/openings/pins/objects/boundaryPolygon). iOS continues to upload positions in the RoomPlan-native frame; the server applies the view-alignment intake transform once at `/scans/mobile-upload` (see docs/lidar-contract-v4.md). */
2145
2140
  roomPlanJSON: {
2146
2141
  rooms: {
2147
2142
  label?: string;
2148
2143
  name?: string;
2149
2144
  category?: string;
2150
- walls?: unknown[];
2145
+ roomLocalId?: string;
2146
+ walls?: {
2147
+ start?: {
2148
+ x: number;
2149
+ y: number;
2150
+ };
2151
+ end?: {
2152
+ x: number;
2153
+ y: number;
2154
+ };
2155
+ length?: number;
2156
+ height?: number;
2157
+ captureMetadata?: {
2158
+ identifier?: string;
2159
+ isDoor?: boolean;
2160
+ isOpening?: boolean;
2161
+ };
2162
+ captureMethod?: string;
2163
+ }[];
2151
2164
  doors?: {
2152
2165
  position: {
2153
2166
  x: number;
2154
2167
  y: number;
2155
- z: number;
2156
2168
  };
2157
2169
  width: number;
2158
2170
  height: number;
@@ -2167,7 +2179,6 @@ export interface components {
2167
2179
  position: {
2168
2180
  x: number;
2169
2181
  y: number;
2170
- z: number;
2171
2182
  };
2172
2183
  width: number;
2173
2184
  height: number;
@@ -2182,7 +2193,6 @@ export interface components {
2182
2193
  position: {
2183
2194
  x: number;
2184
2195
  y: number;
2185
- z: number;
2186
2196
  };
2187
2197
  width: number;
2188
2198
  height: number;
@@ -2193,6 +2203,35 @@ export interface components {
2193
2203
  /** @enum {string} */
2194
2204
  placementSource?: "auto" | "manual";
2195
2205
  }[];
2206
+ pins?: {
2207
+ id: string;
2208
+ position: {
2209
+ x: number;
2210
+ y: number;
2211
+ };
2212
+ label?: string;
2213
+ roomLocalId?: string;
2214
+ kind?: string;
2215
+ note?: string;
2216
+ }[];
2217
+ objects?: {
2218
+ category?: string;
2219
+ position?: {
2220
+ x: number;
2221
+ y: number;
2222
+ };
2223
+ dimensions?: {
2224
+ width?: number;
2225
+ height?: number;
2226
+ depth?: number;
2227
+ };
2228
+ }[];
2229
+ boundaryPolygon?: {
2230
+ x: number;
2231
+ y: number;
2232
+ }[];
2233
+ roomNote?: string;
2234
+ entryDoorwayId?: string;
2196
2235
  }[];
2197
2236
  };
2198
2237
  name: string;
@@ -2201,7 +2240,10 @@ export interface components {
2201
2240
  autoMeasure?: boolean;
2202
2241
  cleanupPipelineVersion?: string;
2203
2242
  userOrientationHint?: string;
2204
- wallConfidences?: unknown[];
2243
+ /** @description Per-room per-wall capture confidences. Canonical shape: nested `(number[] | null)[]` parallel to `roomPlanJSON.rooms[]`. Two legacy shapes are still accepted for backwards compatibility but should not be emitted by new clients: a flat `number[]` (treated as room[0] confidences) and an object keyed by `roomLocalId` (or numeric index as string). See `normalizeWallConfidences` in `server/routes/lidar.ts`. */
2244
+ wallConfidences?: (number[] | null)[] | number[] | {
2245
+ [key: string]: number[];
2246
+ };
2205
2247
  deviceTrack?: unknown;
2206
2248
  referencePhotos?: unknown[];
2207
2249
  propertyId?: number;
@@ -2229,6 +2271,14 @@ export interface components {
2229
2271
  sessionId?: string;
2230
2272
  sessionCapturedAt?: string;
2231
2273
  clientMirrorApplied?: boolean;
2274
+ parentScanId?: number;
2275
+ /** @description Append-room doorway link to a target room on the parent composite. Format: `scan-<scanId>-room-<roomLocalId>-(door|opening)-<idx>`. The trailing segment selects kind-specifically — `door` indexes the target room's `doors[]`, `opening` indexes `openings[]`; the two are never folded. `<scanId>` and `<roomLocalId>` must reference a scan + room on the parent composite the iOS client is appending to. */
2276
+ entryDoorwayId?: string;
2277
+ entryDoorwayWallIndex?: number;
2278
+ entryDoorwayPosition?: {
2279
+ x: number;
2280
+ y: number;
2281
+ };
2232
2282
  };
2233
2283
  MobileUploadResponse: {
2234
2284
  scanId: number;