@serviceai/api-spec 1.6.1 → 1.6.2

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.6.1",
5
+ "version": "1.6.2",
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",
@@ -146,7 +146,7 @@
146
146
  "properties": {
147
147
  "apiVersion": {
148
148
  "type": "string",
149
- "example": "1.6.1"
149
+ "example": "1.6.2"
150
150
  },
151
151
  "minClientVersion": {
152
152
  "type": "string",
@@ -6459,6 +6459,418 @@
6459
6459
  "created"
6460
6460
  ],
6461
6461
  "description": "`data` of a successful `draft_checklist_from_job_photos` call (`success: true`)."
6462
+ },
6463
+ "AssetInitUploadRequest": {
6464
+ "type": "object",
6465
+ "properties": {
6466
+ "filename": {
6467
+ "anyOf": [
6468
+ {
6469
+ "type": "string",
6470
+ "maxLength": 10000
6471
+ },
6472
+ {
6473
+ "type": "number"
6474
+ },
6475
+ {
6476
+ "type": "null"
6477
+ }
6478
+ ]
6479
+ },
6480
+ "byteSize": {
6481
+ "anyOf": [
6482
+ {
6483
+ "type": "number"
6484
+ },
6485
+ {
6486
+ "type": "string",
6487
+ "maxLength": 10000
6488
+ },
6489
+ {
6490
+ "type": "null"
6491
+ }
6492
+ ]
6493
+ },
6494
+ "clientQueueId": {
6495
+ "anyOf": [
6496
+ {
6497
+ "type": "string",
6498
+ "maxLength": 10000
6499
+ },
6500
+ {
6501
+ "type": "number"
6502
+ },
6503
+ {
6504
+ "type": "null"
6505
+ }
6506
+ ]
6507
+ },
6508
+ "pendingMetadata": {
6509
+ "type": [
6510
+ "object",
6511
+ "null"
6512
+ ],
6513
+ "additionalProperties": {}
6514
+ },
6515
+ "mimeType": {
6516
+ "anyOf": [
6517
+ {
6518
+ "type": "string",
6519
+ "maxLength": 10000
6520
+ },
6521
+ {
6522
+ "type": "number"
6523
+ },
6524
+ {
6525
+ "type": "null"
6526
+ }
6527
+ ]
6528
+ },
6529
+ "contentType": {
6530
+ "anyOf": [
6531
+ {
6532
+ "type": "string",
6533
+ "maxLength": 10000
6534
+ },
6535
+ {
6536
+ "type": "number"
6537
+ },
6538
+ {
6539
+ "type": "null"
6540
+ }
6541
+ ]
6542
+ },
6543
+ "poster": {
6544
+ "type": [
6545
+ "object",
6546
+ "null"
6547
+ ],
6548
+ "properties": {
6549
+ "contentType": {
6550
+ "type": "string",
6551
+ "enum": [
6552
+ "image/jpeg"
6553
+ ]
6554
+ },
6555
+ "byteSize": {
6556
+ "type": "integer",
6557
+ "exclusiveMinimum": 0,
6558
+ "maximum": 2097152
6559
+ }
6560
+ },
6561
+ "required": [
6562
+ "contentType",
6563
+ "byteSize"
6564
+ ]
6565
+ }
6566
+ },
6567
+ "description": "POST /api/assets/init-upload body. `filename` and `mimeType` (alias `contentType`) are required by the handler. `poster` is accepted only when the upload's content type is `video/*`: a JPEG frame of at most 2 MiB the client renders locally and PUTs to `posterUploadUrl`; on any other content type the response is 400 `poster_requires_video`."
6568
+ },
6569
+ "AssetInitUploadResponse": {
6570
+ "type": "object",
6571
+ "properties": {
6572
+ "assetId": {
6573
+ "type": "string"
6574
+ },
6575
+ "key": {
6576
+ "type": "string",
6577
+ "description": "Storage key of the original (`org/<orgId>/assets/<assetId>/original`)."
6578
+ },
6579
+ "putUrl": {
6580
+ "type": "string",
6581
+ "description": "Presigned PUT for the original. Absent when `alreadyCompleted` is true."
6582
+ },
6583
+ "headersToSend": {
6584
+ "type": "object",
6585
+ "additionalProperties": {
6586
+ "type": "string"
6587
+ }
6588
+ },
6589
+ "putUrlExpiresAt": {
6590
+ "type": "string",
6591
+ "description": "ISO instant; also the expiry of `posterUploadUrl`."
6592
+ },
6593
+ "reused": {
6594
+ "type": "boolean"
6595
+ },
6596
+ "alreadyCompleted": {
6597
+ "type": "boolean"
6598
+ },
6599
+ "posterUploadUrl": {
6600
+ "type": "string",
6601
+ "description": "Presigned PUT (Content-Type: image/jpeg) for the poster frame. Present only when `poster` was sent."
6602
+ },
6603
+ "posterKey": {
6604
+ "type": "string",
6605
+ "description": "The poster's storage key: the asset prefix with `/original` replaced by `/thumb_320`."
6606
+ }
6607
+ },
6608
+ "required": [
6609
+ "assetId",
6610
+ "key"
6611
+ ]
6612
+ },
6613
+ "AssetCompleteUploadRequest": {
6614
+ "type": "object",
6615
+ "properties": {
6616
+ "assetId": {
6617
+ "anyOf": [
6618
+ {
6619
+ "type": "string",
6620
+ "maxLength": 10000
6621
+ },
6622
+ {
6623
+ "type": "number"
6624
+ },
6625
+ {
6626
+ "type": "null"
6627
+ }
6628
+ ]
6629
+ },
6630
+ "clientQueueId": {
6631
+ "anyOf": [
6632
+ {
6633
+ "type": "string",
6634
+ "maxLength": 10000
6635
+ },
6636
+ {
6637
+ "type": "number"
6638
+ },
6639
+ {
6640
+ "type": "null"
6641
+ }
6642
+ ]
6643
+ },
6644
+ "gpsLatitude": {
6645
+ "anyOf": [
6646
+ {
6647
+ "type": "number"
6648
+ },
6649
+ {
6650
+ "type": "string",
6651
+ "maxLength": 10000
6652
+ },
6653
+ {
6654
+ "type": "null"
6655
+ }
6656
+ ]
6657
+ },
6658
+ "gpsLongitude": {
6659
+ "anyOf": [
6660
+ {
6661
+ "type": "number"
6662
+ },
6663
+ {
6664
+ "type": "string",
6665
+ "maxLength": 10000
6666
+ },
6667
+ {
6668
+ "type": "null"
6669
+ }
6670
+ ]
6671
+ },
6672
+ "gpsAddress": {
6673
+ "anyOf": [
6674
+ {
6675
+ "type": "string",
6676
+ "maxLength": 10000
6677
+ },
6678
+ {
6679
+ "type": "number"
6680
+ },
6681
+ {
6682
+ "type": "null"
6683
+ }
6684
+ ]
6685
+ },
6686
+ "captureTimestamp": {
6687
+ "anyOf": [
6688
+ {
6689
+ "type": "string"
6690
+ },
6691
+ {
6692
+ "type": "number"
6693
+ },
6694
+ {
6695
+ "type": "string",
6696
+ "format": "date-time"
6697
+ },
6698
+ {
6699
+ "type": "null"
6700
+ }
6701
+ ]
6702
+ },
6703
+ "deviceInfo": {
6704
+ "anyOf": [
6705
+ {
6706
+ "type": "string",
6707
+ "maxLength": 10000
6708
+ },
6709
+ {
6710
+ "type": "number"
6711
+ },
6712
+ {
6713
+ "type": "null"
6714
+ }
6715
+ ]
6716
+ },
6717
+ "overlayApplied": {
6718
+ "anyOf": [
6719
+ {
6720
+ "type": "boolean"
6721
+ },
6722
+ {
6723
+ "type": "string"
6724
+ },
6725
+ {
6726
+ "type": "number"
6727
+ },
6728
+ {
6729
+ "type": "null"
6730
+ }
6731
+ ]
6732
+ },
6733
+ "hasMarkup": {
6734
+ "anyOf": [
6735
+ {
6736
+ "type": "boolean"
6737
+ },
6738
+ {
6739
+ "type": "string"
6740
+ },
6741
+ {
6742
+ "type": "number"
6743
+ },
6744
+ {
6745
+ "type": "null"
6746
+ }
6747
+ ]
6748
+ },
6749
+ "tags": {
6750
+ "type": [
6751
+ "array",
6752
+ "null"
6753
+ ],
6754
+ "items": {}
6755
+ },
6756
+ "projectId": {
6757
+ "anyOf": [
6758
+ {
6759
+ "type": "number"
6760
+ },
6761
+ {
6762
+ "type": "string",
6763
+ "maxLength": 10000
6764
+ },
6765
+ {
6766
+ "type": "null"
6767
+ }
6768
+ ]
6769
+ },
6770
+ "contactId": {
6771
+ "anyOf": [
6772
+ {
6773
+ "type": "number"
6774
+ },
6775
+ {
6776
+ "type": "string",
6777
+ "maxLength": 10000
6778
+ },
6779
+ {
6780
+ "type": "null"
6781
+ }
6782
+ ]
6783
+ },
6784
+ "folderId": {
6785
+ "anyOf": [
6786
+ {
6787
+ "type": "string",
6788
+ "maxLength": 10000
6789
+ },
6790
+ {
6791
+ "type": "number"
6792
+ },
6793
+ {
6794
+ "type": "null"
6795
+ }
6796
+ ]
6797
+ },
6798
+ "description": {
6799
+ "anyOf": [
6800
+ {
6801
+ "type": "string",
6802
+ "maxLength": 200000
6803
+ },
6804
+ {
6805
+ "type": "number"
6806
+ },
6807
+ {
6808
+ "type": "null"
6809
+ }
6810
+ ]
6811
+ },
6812
+ "serverOverlay": {
6813
+ "type": [
6814
+ "object",
6815
+ "null"
6816
+ ],
6817
+ "additionalProperties": {}
6818
+ },
6819
+ "capturePose": {
6820
+ "type": [
6821
+ "object",
6822
+ "null"
6823
+ ],
6824
+ "additionalProperties": {}
6825
+ },
6826
+ "posterUploaded": {
6827
+ "anyOf": [
6828
+ {
6829
+ "type": "boolean"
6830
+ },
6831
+ {
6832
+ "type": "string"
6833
+ },
6834
+ {
6835
+ "type": "number"
6836
+ },
6837
+ {
6838
+ "type": "null"
6839
+ }
6840
+ ]
6841
+ }
6842
+ },
6843
+ "description": "POST /api/assets/complete-upload body. `assetId` or `clientQueueId` is required. `posterUploaded: true` (video uploads only) asks the server to verify the poster frame PUT to `posterUploadUrl` and use it as the thumbnail at once; a missing, oversized or non-JPEG poster never fails the upload — it completes without it."
6844
+ },
6845
+ "AssetCompleteUploadResponse": {
6846
+ "type": "object",
6847
+ "properties": {
6848
+ "ok": {
6849
+ "type": "boolean",
6850
+ "enum": [
6851
+ true
6852
+ ]
6853
+ },
6854
+ "assetId": {
6855
+ "type": "string"
6856
+ },
6857
+ "alreadyCompleted": {
6858
+ "type": "boolean"
6859
+ },
6860
+ "actions": {
6861
+ "type": "array",
6862
+ "items": {
6863
+ "type": "string"
6864
+ },
6865
+ "description": "Human-readable log of what finalize did (status change, links, pipeline dispatch, `thumbnail: client poster accepted (thumb_320)`)."
6866
+ }
6867
+ },
6868
+ "required": [
6869
+ "ok",
6870
+ "assetId",
6871
+ "alreadyCompleted",
6872
+ "actions"
6873
+ ]
6462
6874
  }
6463
6875
  },
6464
6876
  "parameters": {}
@@ -13014,6 +13426,125 @@
13014
13426
  }
13015
13427
  }
13016
13428
  }
13429
+ },
13430
+ "/api/assets/init-upload": {
13431
+ "post": {
13432
+ "summary": "Open a direct-to-S3 upload: presigned PUT for the original, and for a video's optional poster frame.",
13433
+ "tags": [
13434
+ "photos"
13435
+ ],
13436
+ "parameters": [
13437
+ {
13438
+ "name": "X-Client-Version",
13439
+ "in": "header",
13440
+ "required": false,
13441
+ "schema": {
13442
+ "type": "string",
13443
+ "example": "1.0.0"
13444
+ },
13445
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
13446
+ }
13447
+ ],
13448
+ "requestBody": {
13449
+ "required": true,
13450
+ "content": {
13451
+ "application/json": {
13452
+ "schema": {
13453
+ "$ref": "#/components/schemas/AssetInitUploadRequest"
13454
+ }
13455
+ }
13456
+ }
13457
+ },
13458
+ "responses": {
13459
+ "200": {
13460
+ "description": "Presigned URL(s) and the asset id to pass to complete-upload.",
13461
+ "content": {
13462
+ "application/json": {
13463
+ "schema": {
13464
+ "$ref": "#/components/schemas/AssetInitUploadResponse"
13465
+ }
13466
+ }
13467
+ }
13468
+ },
13469
+ "400": {
13470
+ "description": "Missing filename/mimeType, or a `poster` on a non-video upload (`poster_requires_video`).",
13471
+ "content": {
13472
+ "application/json": {
13473
+ "schema": {
13474
+ "$ref": "#/components/schemas/ErrorEnvelope"
13475
+ }
13476
+ }
13477
+ }
13478
+ },
13479
+ "401": {
13480
+ "description": "Authentication required."
13481
+ },
13482
+ "413": {
13483
+ "description": "File larger than the single-PUT limit."
13484
+ }
13485
+ }
13486
+ }
13487
+ },
13488
+ "/api/assets/complete-upload": {
13489
+ "post": {
13490
+ "summary": "Mark a direct upload complete (idempotent); link it to a job/contact; dispatch processing.",
13491
+ "tags": [
13492
+ "photos"
13493
+ ],
13494
+ "parameters": [
13495
+ {
13496
+ "name": "X-Client-Version",
13497
+ "in": "header",
13498
+ "required": false,
13499
+ "schema": {
13500
+ "type": "string",
13501
+ "example": "1.0.0"
13502
+ },
13503
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
13504
+ }
13505
+ ],
13506
+ "requestBody": {
13507
+ "required": true,
13508
+ "content": {
13509
+ "application/json": {
13510
+ "schema": {
13511
+ "$ref": "#/components/schemas/AssetCompleteUploadRequest"
13512
+ }
13513
+ }
13514
+ }
13515
+ },
13516
+ "responses": {
13517
+ "200": {
13518
+ "description": "Finalized (or already was).",
13519
+ "content": {
13520
+ "application/json": {
13521
+ "schema": {
13522
+ "$ref": "#/components/schemas/AssetCompleteUploadResponse"
13523
+ }
13524
+ }
13525
+ }
13526
+ },
13527
+ "400": {
13528
+ "description": "Neither assetId nor clientQueueId.",
13529
+ "content": {
13530
+ "application/json": {
13531
+ "schema": {
13532
+ "$ref": "#/components/schemas/ErrorEnvelope"
13533
+ }
13534
+ }
13535
+ }
13536
+ },
13537
+ "401": {
13538
+ "description": "Authentication required."
13539
+ },
13540
+ "403": {
13541
+ "description": "Attaching to a job/contact without the Upload Files permission."
13542
+ },
13543
+ "404": {
13544
+ "description": "Asset, job or contact not found in the organization."
13545
+ }
13546
+ }
13547
+ }
13017
13548
  }
13018
13549
  },
13019
13550
  "webhooks": {}
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.6.1
4
+ version: 1.6.2
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
@@ -103,7 +103,7 @@ components:
103
103
  properties:
104
104
  apiVersion:
105
105
  type: string
106
- example: 1.6.1
106
+ example: 1.6.2
107
107
  minClientVersion:
108
108
  type: string
109
109
  example: 1.0.0
@@ -4514,6 +4514,226 @@ components:
4514
4514
  - unsortedPhotos
4515
4515
  - created
4516
4516
  description: '`data` of a successful `draft_checklist_from_job_photos` call (`success: true`).'
4517
+ AssetInitUploadRequest:
4518
+ type: object
4519
+ properties:
4520
+ filename:
4521
+ anyOf:
4522
+ - type: string
4523
+ maxLength: 10000
4524
+ - type: number
4525
+ - type: 'null'
4526
+ byteSize:
4527
+ anyOf:
4528
+ - type: number
4529
+ - type: string
4530
+ maxLength: 10000
4531
+ - type: 'null'
4532
+ clientQueueId:
4533
+ anyOf:
4534
+ - type: string
4535
+ maxLength: 10000
4536
+ - type: number
4537
+ - type: 'null'
4538
+ pendingMetadata:
4539
+ type:
4540
+ - object
4541
+ - 'null'
4542
+ additionalProperties: {}
4543
+ mimeType:
4544
+ anyOf:
4545
+ - type: string
4546
+ maxLength: 10000
4547
+ - type: number
4548
+ - type: 'null'
4549
+ contentType:
4550
+ anyOf:
4551
+ - type: string
4552
+ maxLength: 10000
4553
+ - type: number
4554
+ - type: 'null'
4555
+ poster:
4556
+ type:
4557
+ - object
4558
+ - 'null'
4559
+ properties:
4560
+ contentType:
4561
+ type: string
4562
+ enum:
4563
+ - image/jpeg
4564
+ byteSize:
4565
+ type: integer
4566
+ exclusiveMinimum: 0
4567
+ maximum: 2097152
4568
+ required:
4569
+ - contentType
4570
+ - byteSize
4571
+ description: >-
4572
+ POST /api/assets/init-upload body. `filename` and `mimeType` (alias `contentType`) are required by the handler.
4573
+ `poster` is accepted only when the upload's content type is `video/*`: a JPEG frame of at most 2 MiB the client
4574
+ renders locally and PUTs to `posterUploadUrl`; on any other content type the response is 400
4575
+ `poster_requires_video`.
4576
+ AssetInitUploadResponse:
4577
+ type: object
4578
+ properties:
4579
+ assetId:
4580
+ type: string
4581
+ key:
4582
+ type: string
4583
+ description: Storage key of the original (`org/<orgId>/assets/<assetId>/original`).
4584
+ putUrl:
4585
+ type: string
4586
+ description: Presigned PUT for the original. Absent when `alreadyCompleted` is true.
4587
+ headersToSend:
4588
+ type: object
4589
+ additionalProperties:
4590
+ type: string
4591
+ putUrlExpiresAt:
4592
+ type: string
4593
+ description: ISO instant; also the expiry of `posterUploadUrl`.
4594
+ reused:
4595
+ type: boolean
4596
+ alreadyCompleted:
4597
+ type: boolean
4598
+ posterUploadUrl:
4599
+ type: string
4600
+ description: 'Presigned PUT (Content-Type: image/jpeg) for the poster frame. Present only when `poster` was sent.'
4601
+ posterKey:
4602
+ type: string
4603
+ description: 'The poster''s storage key: the asset prefix with `/original` replaced by `/thumb_320`.'
4604
+ required:
4605
+ - assetId
4606
+ - key
4607
+ AssetCompleteUploadRequest:
4608
+ type: object
4609
+ properties:
4610
+ assetId:
4611
+ anyOf:
4612
+ - type: string
4613
+ maxLength: 10000
4614
+ - type: number
4615
+ - type: 'null'
4616
+ clientQueueId:
4617
+ anyOf:
4618
+ - type: string
4619
+ maxLength: 10000
4620
+ - type: number
4621
+ - type: 'null'
4622
+ gpsLatitude:
4623
+ anyOf:
4624
+ - type: number
4625
+ - type: string
4626
+ maxLength: 10000
4627
+ - type: 'null'
4628
+ gpsLongitude:
4629
+ anyOf:
4630
+ - type: number
4631
+ - type: string
4632
+ maxLength: 10000
4633
+ - type: 'null'
4634
+ gpsAddress:
4635
+ anyOf:
4636
+ - type: string
4637
+ maxLength: 10000
4638
+ - type: number
4639
+ - type: 'null'
4640
+ captureTimestamp:
4641
+ anyOf:
4642
+ - type: string
4643
+ - type: number
4644
+ - type: string
4645
+ format: date-time
4646
+ - type: 'null'
4647
+ deviceInfo:
4648
+ anyOf:
4649
+ - type: string
4650
+ maxLength: 10000
4651
+ - type: number
4652
+ - type: 'null'
4653
+ overlayApplied:
4654
+ anyOf:
4655
+ - type: boolean
4656
+ - type: string
4657
+ - type: number
4658
+ - type: 'null'
4659
+ hasMarkup:
4660
+ anyOf:
4661
+ - type: boolean
4662
+ - type: string
4663
+ - type: number
4664
+ - type: 'null'
4665
+ tags:
4666
+ type:
4667
+ - array
4668
+ - 'null'
4669
+ items: {}
4670
+ projectId:
4671
+ anyOf:
4672
+ - type: number
4673
+ - type: string
4674
+ maxLength: 10000
4675
+ - type: 'null'
4676
+ contactId:
4677
+ anyOf:
4678
+ - type: number
4679
+ - type: string
4680
+ maxLength: 10000
4681
+ - type: 'null'
4682
+ folderId:
4683
+ anyOf:
4684
+ - type: string
4685
+ maxLength: 10000
4686
+ - type: number
4687
+ - type: 'null'
4688
+ description:
4689
+ anyOf:
4690
+ - type: string
4691
+ maxLength: 200000
4692
+ - type: number
4693
+ - type: 'null'
4694
+ serverOverlay:
4695
+ type:
4696
+ - object
4697
+ - 'null'
4698
+ additionalProperties: {}
4699
+ capturePose:
4700
+ type:
4701
+ - object
4702
+ - 'null'
4703
+ additionalProperties: {}
4704
+ posterUploaded:
4705
+ anyOf:
4706
+ - type: boolean
4707
+ - type: string
4708
+ - type: number
4709
+ - type: 'null'
4710
+ description: >-
4711
+ POST /api/assets/complete-upload body. `assetId` or `clientQueueId` is required. `posterUploaded: true` (video
4712
+ uploads only) asks the server to verify the poster frame PUT to `posterUploadUrl` and use it as the thumbnail at
4713
+ once; a missing, oversized or non-JPEG poster never fails the upload — it completes without it.
4714
+ AssetCompleteUploadResponse:
4715
+ type: object
4716
+ properties:
4717
+ ok:
4718
+ type: boolean
4719
+ enum:
4720
+ - true
4721
+ assetId:
4722
+ type: string
4723
+ alreadyCompleted:
4724
+ type: boolean
4725
+ actions:
4726
+ type: array
4727
+ items:
4728
+ type: string
4729
+ description: >-
4730
+ Human-readable log of what finalize did (status change, links, pipeline dispatch, `thumbnail: client poster
4731
+ accepted (thumb_320)`).
4732
+ required:
4733
+ - ok
4734
+ - assetId
4735
+ - alreadyCompleted
4736
+ - actions
4517
4737
  parameters: {}
4518
4738
  paths:
4519
4739
  /api/version:
@@ -8700,4 +8920,66 @@ paths:
8700
8920
  application/json:
8701
8921
  schema:
8702
8922
  $ref: '#/components/schemas/ErrorEnvelope'
8923
+ /api/assets/init-upload:
8924
+ post:
8925
+ summary: 'Open a direct-to-S3 upload: presigned PUT for the original, and for a video''s optional poster frame.'
8926
+ tags:
8927
+ - photos
8928
+ parameters:
8929
+ - *ref_0
8930
+ requestBody:
8931
+ required: true
8932
+ content:
8933
+ application/json:
8934
+ schema:
8935
+ $ref: '#/components/schemas/AssetInitUploadRequest'
8936
+ responses:
8937
+ '200':
8938
+ description: Presigned URL(s) and the asset id to pass to complete-upload.
8939
+ content:
8940
+ application/json:
8941
+ schema:
8942
+ $ref: '#/components/schemas/AssetInitUploadResponse'
8943
+ '400':
8944
+ description: Missing filename/mimeType, or a `poster` on a non-video upload (`poster_requires_video`).
8945
+ content:
8946
+ application/json:
8947
+ schema:
8948
+ $ref: '#/components/schemas/ErrorEnvelope'
8949
+ '401':
8950
+ description: Authentication required.
8951
+ '413':
8952
+ description: File larger than the single-PUT limit.
8953
+ /api/assets/complete-upload:
8954
+ post:
8955
+ summary: Mark a direct upload complete (idempotent); link it to a job/contact; dispatch processing.
8956
+ tags:
8957
+ - photos
8958
+ parameters:
8959
+ - *ref_0
8960
+ requestBody:
8961
+ required: true
8962
+ content:
8963
+ application/json:
8964
+ schema:
8965
+ $ref: '#/components/schemas/AssetCompleteUploadRequest'
8966
+ responses:
8967
+ '200':
8968
+ description: Finalized (or already was).
8969
+ content:
8970
+ application/json:
8971
+ schema:
8972
+ $ref: '#/components/schemas/AssetCompleteUploadResponse'
8973
+ '400':
8974
+ description: Neither assetId nor clientQueueId.
8975
+ content:
8976
+ application/json:
8977
+ schema:
8978
+ $ref: '#/components/schemas/ErrorEnvelope'
8979
+ '401':
8980
+ description: Authentication required.
8981
+ '403':
8982
+ description: Attaching to a job/contact without the Upload Files permission.
8983
+ '404':
8984
+ description: Asset, job or contact not found in the organization.
8703
8985
  webhooks: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serviceai/api-spec",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
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
@@ -4237,6 +4237,145 @@ export interface paths {
4237
4237
  patch?: never;
4238
4238
  trace?: never;
4239
4239
  };
4240
+ "/api/assets/init-upload": {
4241
+ parameters: {
4242
+ query?: never;
4243
+ header?: never;
4244
+ path?: never;
4245
+ cookie?: never;
4246
+ };
4247
+ get?: never;
4248
+ put?: never;
4249
+ /** Open a direct-to-S3 upload: presigned PUT for the original, and for a video's optional poster frame. */
4250
+ post: {
4251
+ parameters: {
4252
+ query?: never;
4253
+ header?: {
4254
+ /** @description Semver of the calling app build. Drives the 426 / soft-upgrade handshake. */
4255
+ "X-Client-Version"?: string;
4256
+ };
4257
+ path?: never;
4258
+ cookie?: never;
4259
+ };
4260
+ requestBody: {
4261
+ content: {
4262
+ "application/json": components["schemas"]["AssetInitUploadRequest"];
4263
+ };
4264
+ };
4265
+ responses: {
4266
+ /** @description Presigned URL(s) and the asset id to pass to complete-upload. */
4267
+ 200: {
4268
+ headers: {
4269
+ [name: string]: unknown;
4270
+ };
4271
+ content: {
4272
+ "application/json": components["schemas"]["AssetInitUploadResponse"];
4273
+ };
4274
+ };
4275
+ /** @description Missing filename/mimeType, or a `poster` on a non-video upload (`poster_requires_video`). */
4276
+ 400: {
4277
+ headers: {
4278
+ [name: string]: unknown;
4279
+ };
4280
+ content: {
4281
+ "application/json": components["schemas"]["ErrorEnvelope"];
4282
+ };
4283
+ };
4284
+ /** @description Authentication required. */
4285
+ 401: {
4286
+ headers: {
4287
+ [name: string]: unknown;
4288
+ };
4289
+ content?: never;
4290
+ };
4291
+ /** @description File larger than the single-PUT limit. */
4292
+ 413: {
4293
+ headers: {
4294
+ [name: string]: unknown;
4295
+ };
4296
+ content?: never;
4297
+ };
4298
+ };
4299
+ };
4300
+ delete?: never;
4301
+ options?: never;
4302
+ head?: never;
4303
+ patch?: never;
4304
+ trace?: never;
4305
+ };
4306
+ "/api/assets/complete-upload": {
4307
+ parameters: {
4308
+ query?: never;
4309
+ header?: never;
4310
+ path?: never;
4311
+ cookie?: never;
4312
+ };
4313
+ get?: never;
4314
+ put?: never;
4315
+ /** Mark a direct upload complete (idempotent); link it to a job/contact; dispatch processing. */
4316
+ post: {
4317
+ parameters: {
4318
+ query?: never;
4319
+ header?: {
4320
+ /** @description Semver of the calling app build. Drives the 426 / soft-upgrade handshake. */
4321
+ "X-Client-Version"?: string;
4322
+ };
4323
+ path?: never;
4324
+ cookie?: never;
4325
+ };
4326
+ requestBody: {
4327
+ content: {
4328
+ "application/json": components["schemas"]["AssetCompleteUploadRequest"];
4329
+ };
4330
+ };
4331
+ responses: {
4332
+ /** @description Finalized (or already was). */
4333
+ 200: {
4334
+ headers: {
4335
+ [name: string]: unknown;
4336
+ };
4337
+ content: {
4338
+ "application/json": components["schemas"]["AssetCompleteUploadResponse"];
4339
+ };
4340
+ };
4341
+ /** @description Neither assetId nor clientQueueId. */
4342
+ 400: {
4343
+ headers: {
4344
+ [name: string]: unknown;
4345
+ };
4346
+ content: {
4347
+ "application/json": components["schemas"]["ErrorEnvelope"];
4348
+ };
4349
+ };
4350
+ /** @description Authentication required. */
4351
+ 401: {
4352
+ headers: {
4353
+ [name: string]: unknown;
4354
+ };
4355
+ content?: never;
4356
+ };
4357
+ /** @description Attaching to a job/contact without the Upload Files permission. */
4358
+ 403: {
4359
+ headers: {
4360
+ [name: string]: unknown;
4361
+ };
4362
+ content?: never;
4363
+ };
4364
+ /** @description Asset, job or contact not found in the organization. */
4365
+ 404: {
4366
+ headers: {
4367
+ [name: string]: unknown;
4368
+ };
4369
+ content?: never;
4370
+ };
4371
+ };
4372
+ };
4373
+ delete?: never;
4374
+ options?: never;
4375
+ head?: never;
4376
+ patch?: never;
4377
+ trace?: never;
4378
+ };
4240
4379
  }
4241
4380
  export type webhooks = Record<string, never>;
4242
4381
  export interface components {
@@ -4261,7 +4400,7 @@ export interface components {
4261
4400
  };
4262
4401
  /** @description Cross-platform version handshake manifest. See docs/api-versioning.md. */
4263
4402
  VersionManifest: {
4264
- /** @example 1.6.1 */
4403
+ /** @example 1.6.2 */
4265
4404
  apiVersion: string;
4266
4405
  /** @example 1.0.0 */
4267
4406
  minClientVersion: string;
@@ -5724,6 +5863,72 @@ export interface components {
5724
5863
  unsortedPhotos: number;
5725
5864
  created: boolean;
5726
5865
  };
5866
+ /** @description POST /api/assets/init-upload body. `filename` and `mimeType` (alias `contentType`) are required by the handler. `poster` is accepted only when the upload's content type is `video/*`: a JPEG frame of at most 2 MiB the client renders locally and PUTs to `posterUploadUrl`; on any other content type the response is 400 `poster_requires_video`. */
5867
+ AssetInitUploadRequest: {
5868
+ filename?: string | number | null;
5869
+ byteSize?: number | string | null;
5870
+ clientQueueId?: string | number | null;
5871
+ pendingMetadata?: {
5872
+ [key: string]: unknown;
5873
+ } | null;
5874
+ mimeType?: string | number | null;
5875
+ contentType?: string | number | null;
5876
+ poster?: {
5877
+ /** @enum {string} */
5878
+ contentType: "image/jpeg";
5879
+ byteSize: number;
5880
+ } | null;
5881
+ };
5882
+ AssetInitUploadResponse: {
5883
+ assetId: string;
5884
+ /** @description Storage key of the original (`org/<orgId>/assets/<assetId>/original`). */
5885
+ key: string;
5886
+ /** @description Presigned PUT for the original. Absent when `alreadyCompleted` is true. */
5887
+ putUrl?: string;
5888
+ headersToSend?: {
5889
+ [key: string]: string;
5890
+ };
5891
+ /** @description ISO instant; also the expiry of `posterUploadUrl`. */
5892
+ putUrlExpiresAt?: string;
5893
+ reused?: boolean;
5894
+ alreadyCompleted?: boolean;
5895
+ /** @description Presigned PUT (Content-Type: image/jpeg) for the poster frame. Present only when `poster` was sent. */
5896
+ posterUploadUrl?: string;
5897
+ /** @description The poster's storage key: the asset prefix with `/original` replaced by `/thumb_320`. */
5898
+ posterKey?: string;
5899
+ };
5900
+ /** @description POST /api/assets/complete-upload body. `assetId` or `clientQueueId` is required. `posterUploaded: true` (video uploads only) asks the server to verify the poster frame PUT to `posterUploadUrl` and use it as the thumbnail at once; a missing, oversized or non-JPEG poster never fails the upload — it completes without it. */
5901
+ AssetCompleteUploadRequest: {
5902
+ assetId?: string | number | null;
5903
+ clientQueueId?: string | number | null;
5904
+ gpsLatitude?: number | string | null;
5905
+ gpsLongitude?: number | string | null;
5906
+ gpsAddress?: string | number | null;
5907
+ captureTimestamp?: string | number | null;
5908
+ deviceInfo?: string | number | null;
5909
+ overlayApplied?: boolean | string | number | null;
5910
+ hasMarkup?: boolean | string | number | null;
5911
+ tags?: unknown[] | null;
5912
+ projectId?: number | string | null;
5913
+ contactId?: number | string | null;
5914
+ folderId?: string | number | null;
5915
+ description?: string | number | null;
5916
+ serverOverlay?: {
5917
+ [key: string]: unknown;
5918
+ } | null;
5919
+ capturePose?: {
5920
+ [key: string]: unknown;
5921
+ } | null;
5922
+ posterUploaded?: boolean | string | number | null;
5923
+ };
5924
+ AssetCompleteUploadResponse: {
5925
+ /** @enum {boolean} */
5926
+ ok: true;
5927
+ assetId: string;
5928
+ alreadyCompleted: boolean;
5929
+ /** @description Human-readable log of what finalize did (status change, links, pipeline dispatch, `thumbnail: client poster accepted (thumb_320)`). */
5930
+ actions: string[];
5931
+ };
5727
5932
  };
5728
5933
  responses: never;
5729
5934
  parameters: never;