@serviceai/api-spec 1.1.23 → 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.
Files changed (4) hide show
  1. package/openapi.json +1325 -107
  2. package/openapi.yaml +806 -34
  3. package/package.json +1 -1
  4. package/types.d.ts +678 -13
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.23",
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.23"
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",
@@ -2598,62 +2816,316 @@
2598
2816
  "mirrorX"
2599
2817
  ],
2600
2818
  "description": "Persisted floor-plan PNG render-frame. `mirrorX` is derived from `scans.client_mirror_applied` (negate when false). Null sentinel: ANY required column NULL → `null` is emitted instead of a partial frame."
2601
- }
2602
- },
2603
- "parameters": {}
2604
- },
2605
- "paths": {
2606
- "/api/version": {
2607
- "get": {
2608
- "summary": "Cross-platform version handshake manifest.",
2609
- "tags": [
2610
- "version"
2611
- ],
2612
- "responses": {
2613
- "200": {
2614
- "description": "Always returned. Body is the canonical handshake manifest.",
2615
- "content": {
2616
- "application/json": {
2617
- "schema": {
2618
- "$ref": "#/components/schemas/VersionManifest"
2619
- }
2620
- }
2621
- }
2622
- }
2623
- }
2624
- }
2625
- },
2626
- "/api/ping": {
2627
- "get": {
2628
- "summary": "Liveness probe. Bypasses the version handshake.",
2629
- "tags": [
2630
- "version"
2631
- ],
2632
- "responses": {
2633
- "200": {
2634
- "description": "Static OK envelope.",
2635
- "content": {
2636
- "application/json": {
2637
- "schema": {
2638
- "type": "object",
2639
- "properties": {
2640
- "status": {
2641
- "type": "string",
2642
- "enum": [
2643
- "ok"
2644
- ]
2645
- },
2646
- "timestamp": {
2647
- "type": "string"
2648
- },
2649
- "env": {
2650
- "type": [
2651
- "string",
2652
- "null"
2653
- ]
2654
- }
2819
+ },
2820
+ "ChattyToolDefinition": {
2821
+ "type": "object",
2822
+ "properties": {
2823
+ "type": {
2824
+ "type": "string",
2825
+ "enum": [
2826
+ "function"
2827
+ ]
2828
+ },
2829
+ "function": {
2830
+ "type": "object",
2831
+ "properties": {
2832
+ "name": {
2833
+ "type": "string",
2834
+ "minLength": 1
2835
+ },
2836
+ "description": {
2837
+ "type": "string"
2838
+ },
2839
+ "parameters": {
2840
+ "type": "object",
2841
+ "properties": {
2842
+ "type": {
2843
+ "type": "string",
2844
+ "enum": [
2845
+ "object"
2846
+ ]
2655
2847
  },
2656
- "required": [
2848
+ "properties": {
2849
+ "type": "object",
2850
+ "additionalProperties": {
2851
+ "allOf": [
2852
+ {
2853
+ "type": "object",
2854
+ "additionalProperties": {}
2855
+ },
2856
+ {
2857
+ "type": "object",
2858
+ "properties": {
2859
+ "type": {
2860
+ "anyOf": [
2861
+ {
2862
+ "type": "string"
2863
+ },
2864
+ {
2865
+ "type": "array",
2866
+ "items": {
2867
+ "type": "string"
2868
+ }
2869
+ }
2870
+ ]
2871
+ },
2872
+ "description": {
2873
+ "type": "string"
2874
+ },
2875
+ "enum": {
2876
+ "type": "array",
2877
+ "items": {
2878
+ "type": "string"
2879
+ }
2880
+ },
2881
+ "required": {
2882
+ "type": "array",
2883
+ "items": {
2884
+ "type": "string"
2885
+ }
2886
+ },
2887
+ "additionalProperties": {
2888
+ "type": "boolean"
2889
+ }
2890
+ },
2891
+ "required": [
2892
+ "type"
2893
+ ]
2894
+ }
2895
+ ]
2896
+ }
2897
+ },
2898
+ "required": {
2899
+ "type": "array",
2900
+ "items": {
2901
+ "type": "string"
2902
+ }
2903
+ },
2904
+ "additionalProperties": {
2905
+ "type": "boolean"
2906
+ }
2907
+ },
2908
+ "required": [
2909
+ "type",
2910
+ "properties"
2911
+ ]
2912
+ }
2913
+ },
2914
+ "required": [
2915
+ "name",
2916
+ "description",
2917
+ "parameters"
2918
+ ]
2919
+ }
2920
+ },
2921
+ "required": [
2922
+ "type",
2923
+ "function"
2924
+ ]
2925
+ },
2926
+ "ChattyExecuteRequest": {
2927
+ "type": "object",
2928
+ "properties": {
2929
+ "function": {
2930
+ "type": "string",
2931
+ "minLength": 1
2932
+ },
2933
+ "name": {
2934
+ "type": "string",
2935
+ "minLength": 1
2936
+ },
2937
+ "arguments": {
2938
+ "type": "object",
2939
+ "additionalProperties": {}
2940
+ }
2941
+ }
2942
+ },
2943
+ "ChattyExecuteResponse": {
2944
+ "type": "object",
2945
+ "properties": {
2946
+ "success": {
2947
+ "type": "boolean"
2948
+ },
2949
+ "message": {
2950
+ "type": "string"
2951
+ },
2952
+ "error": {
2953
+ "type": "string"
2954
+ },
2955
+ "code": {
2956
+ "type": "string"
2957
+ },
2958
+ "data": {},
2959
+ "action": {}
2960
+ },
2961
+ "required": [
2962
+ "success"
2963
+ ]
2964
+ },
2965
+ "ChattyAiContextResponse": {
2966
+ "type": "object",
2967
+ "properties": {
2968
+ "instructions": {
2969
+ "type": "string"
2970
+ },
2971
+ "knowledgeBaseLoaded": {
2972
+ "type": "boolean"
2973
+ },
2974
+ "workflowStagesLoaded": {
2975
+ "type": "boolean"
2976
+ },
2977
+ "timestamp": {
2978
+ "type": "string"
2979
+ }
2980
+ },
2981
+ "required": [
2982
+ "instructions",
2983
+ "knowledgeBaseLoaded",
2984
+ "workflowStagesLoaded",
2985
+ "timestamp"
2986
+ ]
2987
+ },
2988
+ "RealtimeTokenRequest": {
2989
+ "type": "object",
2990
+ "properties": {
2991
+ "directWebRTC": {
2992
+ "type": "boolean"
2993
+ }
2994
+ }
2995
+ },
2996
+ "RealtimeTokenResponse": {
2997
+ "type": "object",
2998
+ "properties": {
2999
+ "sessionId": {
3000
+ "type": "string"
3001
+ },
3002
+ "model": {
3003
+ "type": "string"
3004
+ },
3005
+ "voice": {
3006
+ "type": "string"
3007
+ },
3008
+ "clientSecret": {
3009
+ "type": "string"
3010
+ },
3011
+ "fallback": {
3012
+ "type": "boolean"
3013
+ },
3014
+ "directWebRTC": {
3015
+ "type": "boolean"
3016
+ },
3017
+ "useWebRTC": {
3018
+ "type": "boolean"
3019
+ },
3020
+ "expiresInSeconds": {
3021
+ "type": "integer",
3022
+ "minimum": 0
3023
+ },
3024
+ "metadata": {
3025
+ "type": "object",
3026
+ "properties": {
3027
+ "organizationId": {
3028
+ "anyOf": [
3029
+ {
3030
+ "type": "string"
3031
+ },
3032
+ {
3033
+ "type": "number"
3034
+ }
3035
+ ]
3036
+ }
3037
+ },
3038
+ "required": [
3039
+ "organizationId"
3040
+ ]
3041
+ },
3042
+ "message": {
3043
+ "type": "string"
3044
+ }
3045
+ },
3046
+ "required": [
3047
+ "fallback"
3048
+ ]
3049
+ },
3050
+ "CsrfTokenResponse": {
3051
+ "type": "object",
3052
+ "properties": {
3053
+ "token": {
3054
+ "type": "string",
3055
+ "minLength": 1
3056
+ }
3057
+ },
3058
+ "required": [
3059
+ "token"
3060
+ ]
3061
+ },
3062
+ "RealtimeWebrtcRequest": {
3063
+ "type": "object",
3064
+ "properties": {
3065
+ "sdp": {
3066
+ "type": "string",
3067
+ "minLength": 1
3068
+ }
3069
+ },
3070
+ "required": [
3071
+ "sdp"
3072
+ ]
3073
+ }
3074
+ },
3075
+ "parameters": {}
3076
+ },
3077
+ "paths": {
3078
+ "/api/version": {
3079
+ "get": {
3080
+ "summary": "Cross-platform version handshake manifest.",
3081
+ "tags": [
3082
+ "version"
3083
+ ],
3084
+ "responses": {
3085
+ "200": {
3086
+ "description": "Always returned. Body is the canonical handshake manifest.",
3087
+ "content": {
3088
+ "application/json": {
3089
+ "schema": {
3090
+ "$ref": "#/components/schemas/VersionManifest"
3091
+ }
3092
+ }
3093
+ }
3094
+ }
3095
+ }
3096
+ }
3097
+ },
3098
+ "/api/ping": {
3099
+ "get": {
3100
+ "summary": "Liveness probe. Bypasses the version handshake.",
3101
+ "tags": [
3102
+ "version"
3103
+ ],
3104
+ "responses": {
3105
+ "200": {
3106
+ "description": "Static OK envelope.",
3107
+ "content": {
3108
+ "application/json": {
3109
+ "schema": {
3110
+ "type": "object",
3111
+ "properties": {
3112
+ "status": {
3113
+ "type": "string",
3114
+ "enum": [
3115
+ "ok"
3116
+ ]
3117
+ },
3118
+ "timestamp": {
3119
+ "type": "string"
3120
+ },
3121
+ "env": {
3122
+ "type": [
3123
+ "string",
3124
+ "null"
3125
+ ]
3126
+ }
3127
+ },
3128
+ "required": [
2657
3129
  "status",
2658
3130
  "timestamp",
2659
3131
  "env"
@@ -4505,15 +4977,11 @@
4505
4977
  },
4506
4978
  "y": {
4507
4979
  "type": "number"
4508
- },
4509
- "z": {
4510
- "type": "number"
4511
4980
  }
4512
4981
  },
4513
4982
  "required": [
4514
4983
  "x",
4515
- "y",
4516
- "z"
4984
+ "y"
4517
4985
  ]
4518
4986
  },
4519
4987
  "width": {
@@ -4572,15 +5040,11 @@
4572
5040
  },
4573
5041
  "y": {
4574
5042
  "type": "number"
4575
- },
4576
- "z": {
4577
- "type": "number"
4578
5043
  }
4579
5044
  },
4580
5045
  "required": [
4581
5046
  "x",
4582
- "y",
4583
- "z"
5047
+ "y"
4584
5048
  ]
4585
5049
  },
4586
5050
  "width": {
@@ -4639,15 +5103,11 @@
4639
5103
  },
4640
5104
  "y": {
4641
5105
  "type": "number"
4642
- },
4643
- "z": {
4644
- "type": "number"
4645
5106
  }
4646
5107
  },
4647
5108
  "required": [
4648
5109
  "x",
4649
- "y",
4650
- "z"
5110
+ "y"
4651
5111
  ]
4652
5112
  },
4653
5113
  "width": {
@@ -4887,15 +5347,11 @@
4887
5347
  },
4888
5348
  "y": {
4889
5349
  "type": "number"
4890
- },
4891
- "z": {
4892
- "type": "number"
4893
5350
  }
4894
5351
  },
4895
5352
  "required": [
4896
5353
  "x",
4897
- "y",
4898
- "z"
5354
+ "y"
4899
5355
  ]
4900
5356
  },
4901
5357
  "width": {
@@ -4954,15 +5410,11 @@
4954
5410
  },
4955
5411
  "y": {
4956
5412
  "type": "number"
4957
- },
4958
- "z": {
4959
- "type": "number"
4960
5413
  }
4961
5414
  },
4962
5415
  "required": [
4963
5416
  "x",
4964
- "y",
4965
- "z"
5417
+ "y"
4966
5418
  ]
4967
5419
  },
4968
5420
  "width": {
@@ -5021,15 +5473,11 @@
5021
5473
  },
5022
5474
  "y": {
5023
5475
  "type": "number"
5024
- },
5025
- "z": {
5026
- "type": "number"
5027
5476
  }
5028
5477
  },
5029
5478
  "required": [
5030
5479
  "x",
5031
- "y",
5032
- "z"
5480
+ "y"
5033
5481
  ]
5034
5482
  },
5035
5483
  "width": {
@@ -5138,6 +5586,776 @@
5138
5586
  }
5139
5587
  }
5140
5588
  }
5589
+ },
5590
+ "/api/chatty-voice/tools": {
5591
+ "get": {
5592
+ "summary": "List the OpenAI-Realtime-compatible function tools the executor can run.",
5593
+ "description": "Returns the canonical Chatty Voice tool catalogue as a BARE ARRAY of `ChattyToolDefinition` envelopes (no wrapping object). Each entry is executable via POST /api/chatty-voice/tools/execute.",
5594
+ "tags": [
5595
+ "chatty"
5596
+ ],
5597
+ "parameters": [
5598
+ {
5599
+ "name": "X-Client-Version",
5600
+ "in": "header",
5601
+ "required": false,
5602
+ "schema": {
5603
+ "type": "string",
5604
+ "example": "1.0.0"
5605
+ },
5606
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
5607
+ }
5608
+ ],
5609
+ "responses": {
5610
+ "200": {
5611
+ "description": "Bare array of tool definitions.",
5612
+ "content": {
5613
+ "application/json": {
5614
+ "schema": {
5615
+ "type": "array",
5616
+ "items": {
5617
+ "type": "object",
5618
+ "properties": {
5619
+ "type": {
5620
+ "type": "string",
5621
+ "enum": [
5622
+ "function"
5623
+ ]
5624
+ },
5625
+ "function": {
5626
+ "type": "object",
5627
+ "properties": {
5628
+ "name": {
5629
+ "type": "string",
5630
+ "minLength": 1
5631
+ },
5632
+ "description": {
5633
+ "type": "string"
5634
+ },
5635
+ "parameters": {
5636
+ "type": "object",
5637
+ "properties": {
5638
+ "type": {
5639
+ "type": "string",
5640
+ "enum": [
5641
+ "object"
5642
+ ]
5643
+ },
5644
+ "properties": {
5645
+ "type": "object",
5646
+ "additionalProperties": {
5647
+ "allOf": [
5648
+ {
5649
+ "type": "object",
5650
+ "additionalProperties": {}
5651
+ },
5652
+ {
5653
+ "type": "object",
5654
+ "properties": {
5655
+ "type": {
5656
+ "anyOf": [
5657
+ {
5658
+ "type": "string"
5659
+ },
5660
+ {
5661
+ "type": "array",
5662
+ "items": {
5663
+ "type": "string"
5664
+ }
5665
+ }
5666
+ ]
5667
+ },
5668
+ "description": {
5669
+ "type": "string"
5670
+ },
5671
+ "enum": {
5672
+ "type": "array",
5673
+ "items": {
5674
+ "type": "string"
5675
+ }
5676
+ },
5677
+ "required": {
5678
+ "type": "array",
5679
+ "items": {
5680
+ "type": "string"
5681
+ }
5682
+ },
5683
+ "additionalProperties": {
5684
+ "type": "boolean"
5685
+ }
5686
+ },
5687
+ "required": [
5688
+ "type"
5689
+ ]
5690
+ }
5691
+ ]
5692
+ }
5693
+ },
5694
+ "required": {
5695
+ "type": "array",
5696
+ "items": {
5697
+ "type": "string"
5698
+ }
5699
+ },
5700
+ "additionalProperties": {
5701
+ "type": "boolean"
5702
+ }
5703
+ },
5704
+ "required": [
5705
+ "type",
5706
+ "properties"
5707
+ ]
5708
+ }
5709
+ },
5710
+ "required": [
5711
+ "name",
5712
+ "description",
5713
+ "parameters"
5714
+ ]
5715
+ }
5716
+ },
5717
+ "required": [
5718
+ "type",
5719
+ "function"
5720
+ ]
5721
+ }
5722
+ }
5723
+ }
5724
+ }
5725
+ },
5726
+ "401": {
5727
+ "description": "Unauthenticated.",
5728
+ "content": {
5729
+ "application/json": {
5730
+ "schema": {
5731
+ "$ref": "#/components/schemas/ErrorEnvelope"
5732
+ }
5733
+ }
5734
+ }
5735
+ },
5736
+ "403": {
5737
+ "description": "Caller lacks the chatty-voice entitlement.",
5738
+ "content": {
5739
+ "application/json": {
5740
+ "schema": {
5741
+ "$ref": "#/components/schemas/ErrorEnvelope"
5742
+ }
5743
+ }
5744
+ }
5745
+ },
5746
+ "426": {
5747
+ "description": "Client major below minClientVersion.",
5748
+ "content": {
5749
+ "application/json": {
5750
+ "schema": {
5751
+ "$ref": "#/components/schemas/UpgradeRequiredEnvelope"
5752
+ }
5753
+ }
5754
+ }
5755
+ }
5756
+ }
5757
+ }
5758
+ },
5759
+ "/api/chatty-voice/tools/execute": {
5760
+ "post": {
5761
+ "summary": "Execute a Chatty Voice tool by canonical `function` (or legacy `name`).",
5762
+ "description": "Body accepts either the canonical `function` field or the legacy `name` alias — at least one is required. The executor treats them as equivalent and dispatches to the same switch case in `tools.ts`. Tool arguments are passed through verbatim as an opaque JSON object.",
5763
+ "tags": [
5764
+ "chatty"
5765
+ ],
5766
+ "parameters": [
5767
+ {
5768
+ "name": "X-Client-Version",
5769
+ "in": "header",
5770
+ "required": false,
5771
+ "schema": {
5772
+ "type": "string",
5773
+ "example": "1.0.0"
5774
+ },
5775
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
5776
+ }
5777
+ ],
5778
+ "requestBody": {
5779
+ "content": {
5780
+ "application/json": {
5781
+ "schema": {
5782
+ "type": "object",
5783
+ "properties": {
5784
+ "function": {
5785
+ "type": "string",
5786
+ "minLength": 1
5787
+ },
5788
+ "name": {
5789
+ "type": "string",
5790
+ "minLength": 1
5791
+ },
5792
+ "arguments": {
5793
+ "type": "object",
5794
+ "additionalProperties": {}
5795
+ }
5796
+ }
5797
+ }
5798
+ }
5799
+ }
5800
+ },
5801
+ "responses": {
5802
+ "200": {
5803
+ "description": "Always returned when execution reaches the executor. `success: false` envelopes carry an `error` and (for permission denials) a `code` — including the missing/unknown function case.",
5804
+ "content": {
5805
+ "application/json": {
5806
+ "schema": {
5807
+ "type": "object",
5808
+ "properties": {
5809
+ "success": {
5810
+ "type": "boolean"
5811
+ },
5812
+ "message": {
5813
+ "type": "string"
5814
+ },
5815
+ "error": {
5816
+ "type": "string"
5817
+ },
5818
+ "code": {
5819
+ "type": "string"
5820
+ },
5821
+ "data": {},
5822
+ "action": {}
5823
+ },
5824
+ "required": [
5825
+ "success"
5826
+ ]
5827
+ }
5828
+ }
5829
+ }
5830
+ },
5831
+ "401": {
5832
+ "description": "Unauthenticated.",
5833
+ "content": {
5834
+ "application/json": {
5835
+ "schema": {
5836
+ "$ref": "#/components/schemas/ErrorEnvelope"
5837
+ }
5838
+ }
5839
+ }
5840
+ },
5841
+ "403": {
5842
+ "description": "Caller lacks the chatty-voice entitlement or a per-tool permission.",
5843
+ "content": {
5844
+ "application/json": {
5845
+ "schema": {
5846
+ "$ref": "#/components/schemas/ErrorEnvelope"
5847
+ }
5848
+ }
5849
+ }
5850
+ },
5851
+ "500": {
5852
+ "description": "Unhandled executor error. Body is `{ success: false, error }` (same envelope as 200).",
5853
+ "content": {
5854
+ "application/json": {
5855
+ "schema": {
5856
+ "type": "object",
5857
+ "properties": {
5858
+ "success": {
5859
+ "type": "boolean"
5860
+ },
5861
+ "message": {
5862
+ "type": "string"
5863
+ },
5864
+ "error": {
5865
+ "type": "string"
5866
+ },
5867
+ "code": {
5868
+ "type": "string"
5869
+ },
5870
+ "data": {},
5871
+ "action": {}
5872
+ },
5873
+ "required": [
5874
+ "success"
5875
+ ]
5876
+ }
5877
+ }
5878
+ }
5879
+ }
5880
+ }
5881
+ }
5882
+ },
5883
+ "/api/chatty-voice/ai-context": {
5884
+ "get": {
5885
+ "summary": "Fetch the Chatty Voice system prompt + load-status flags.",
5886
+ "tags": [
5887
+ "chatty"
5888
+ ],
5889
+ "parameters": [
5890
+ {
5891
+ "name": "X-Client-Version",
5892
+ "in": "header",
5893
+ "required": false,
5894
+ "schema": {
5895
+ "type": "string",
5896
+ "example": "1.0.0"
5897
+ },
5898
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
5899
+ }
5900
+ ],
5901
+ "responses": {
5902
+ "200": {
5903
+ "description": "System instructions + knowledge-base / workflow-stage load flags.",
5904
+ "content": {
5905
+ "application/json": {
5906
+ "schema": {
5907
+ "type": "object",
5908
+ "properties": {
5909
+ "instructions": {
5910
+ "type": "string"
5911
+ },
5912
+ "knowledgeBaseLoaded": {
5913
+ "type": "boolean"
5914
+ },
5915
+ "workflowStagesLoaded": {
5916
+ "type": "boolean"
5917
+ },
5918
+ "timestamp": {
5919
+ "type": "string"
5920
+ }
5921
+ },
5922
+ "required": [
5923
+ "instructions",
5924
+ "knowledgeBaseLoaded",
5925
+ "workflowStagesLoaded",
5926
+ "timestamp"
5927
+ ]
5928
+ }
5929
+ }
5930
+ }
5931
+ },
5932
+ "401": {
5933
+ "description": "Unauthenticated.",
5934
+ "content": {
5935
+ "application/json": {
5936
+ "schema": {
5937
+ "$ref": "#/components/schemas/ErrorEnvelope"
5938
+ }
5939
+ }
5940
+ }
5941
+ },
5942
+ "403": {
5943
+ "description": "Caller lacks the chatty-voice entitlement.",
5944
+ "content": {
5945
+ "application/json": {
5946
+ "schema": {
5947
+ "$ref": "#/components/schemas/ErrorEnvelope"
5948
+ }
5949
+ }
5950
+ }
5951
+ }
5952
+ }
5953
+ }
5954
+ },
5955
+ "/api/realtime-token": {
5956
+ "post": {
5957
+ "summary": "Mint an OpenAI Realtime ephemeral session token (single union object).",
5958
+ "description": "Body: `{ directWebRTC?: boolean }`. Response is a SINGLE union object — the directWebRTC, desktop server-proxied, and HTTP-fallback branches all share the same envelope. Only `fallback` is always present; every other field is optional and presence depends on the branch.",
5959
+ "tags": [
5960
+ "chatty"
5961
+ ],
5962
+ "parameters": [
5963
+ {
5964
+ "name": "X-Client-Version",
5965
+ "in": "header",
5966
+ "required": false,
5967
+ "schema": {
5968
+ "type": "string",
5969
+ "example": "1.0.0"
5970
+ },
5971
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
5972
+ }
5973
+ ],
5974
+ "requestBody": {
5975
+ "content": {
5976
+ "application/json": {
5977
+ "schema": {
5978
+ "type": "object",
5979
+ "properties": {
5980
+ "directWebRTC": {
5981
+ "type": "boolean"
5982
+ }
5983
+ }
5984
+ }
5985
+ }
5986
+ }
5987
+ },
5988
+ "responses": {
5989
+ "200": {
5990
+ "description": "Session minted (or fallback signalled). See union shape.",
5991
+ "content": {
5992
+ "application/json": {
5993
+ "schema": {
5994
+ "type": "object",
5995
+ "properties": {
5996
+ "sessionId": {
5997
+ "type": "string"
5998
+ },
5999
+ "model": {
6000
+ "type": "string"
6001
+ },
6002
+ "voice": {
6003
+ "type": "string"
6004
+ },
6005
+ "clientSecret": {
6006
+ "type": "string"
6007
+ },
6008
+ "fallback": {
6009
+ "type": "boolean"
6010
+ },
6011
+ "directWebRTC": {
6012
+ "type": "boolean"
6013
+ },
6014
+ "useWebRTC": {
6015
+ "type": "boolean"
6016
+ },
6017
+ "expiresInSeconds": {
6018
+ "type": "integer",
6019
+ "minimum": 0
6020
+ },
6021
+ "metadata": {
6022
+ "type": "object",
6023
+ "properties": {
6024
+ "organizationId": {
6025
+ "anyOf": [
6026
+ {
6027
+ "type": "string"
6028
+ },
6029
+ {
6030
+ "type": "number"
6031
+ }
6032
+ ]
6033
+ }
6034
+ },
6035
+ "required": [
6036
+ "organizationId"
6037
+ ]
6038
+ },
6039
+ "message": {
6040
+ "type": "string"
6041
+ }
6042
+ },
6043
+ "required": [
6044
+ "fallback"
6045
+ ]
6046
+ }
6047
+ }
6048
+ }
6049
+ },
6050
+ "401": {
6051
+ "description": "Unauthenticated.",
6052
+ "content": {
6053
+ "application/json": {
6054
+ "schema": {
6055
+ "$ref": "#/components/schemas/ErrorEnvelope"
6056
+ }
6057
+ }
6058
+ }
6059
+ },
6060
+ "403": {
6061
+ "description": "Caller lacks the chatty-voice entitlement.",
6062
+ "content": {
6063
+ "application/json": {
6064
+ "schema": {
6065
+ "$ref": "#/components/schemas/ErrorEnvelope"
6066
+ }
6067
+ }
6068
+ }
6069
+ },
6070
+ "405": {
6071
+ "description": "Method Not Allowed (POST only).",
6072
+ "content": {
6073
+ "application/json": {
6074
+ "schema": {
6075
+ "$ref": "#/components/schemas/ErrorEnvelope"
6076
+ }
6077
+ }
6078
+ }
6079
+ },
6080
+ "429": {
6081
+ "description": "Per-user rate limit on token mint.",
6082
+ "content": {
6083
+ "application/json": {
6084
+ "schema": {
6085
+ "$ref": "#/components/schemas/ErrorEnvelope"
6086
+ }
6087
+ }
6088
+ }
6089
+ },
6090
+ "500": {
6091
+ "description": "Mint failure — body still carries `fallback: true`.",
6092
+ "content": {
6093
+ "application/json": {
6094
+ "schema": {
6095
+ "type": "object",
6096
+ "properties": {
6097
+ "sessionId": {
6098
+ "type": "string"
6099
+ },
6100
+ "model": {
6101
+ "type": "string"
6102
+ },
6103
+ "voice": {
6104
+ "type": "string"
6105
+ },
6106
+ "clientSecret": {
6107
+ "type": "string"
6108
+ },
6109
+ "fallback": {
6110
+ "type": "boolean"
6111
+ },
6112
+ "directWebRTC": {
6113
+ "type": "boolean"
6114
+ },
6115
+ "useWebRTC": {
6116
+ "type": "boolean"
6117
+ },
6118
+ "expiresInSeconds": {
6119
+ "type": "integer",
6120
+ "minimum": 0
6121
+ },
6122
+ "metadata": {
6123
+ "type": "object",
6124
+ "properties": {
6125
+ "organizationId": {
6126
+ "anyOf": [
6127
+ {
6128
+ "type": "string"
6129
+ },
6130
+ {
6131
+ "type": "number"
6132
+ }
6133
+ ]
6134
+ }
6135
+ },
6136
+ "required": [
6137
+ "organizationId"
6138
+ ]
6139
+ },
6140
+ "message": {
6141
+ "type": "string"
6142
+ }
6143
+ },
6144
+ "required": [
6145
+ "fallback"
6146
+ ]
6147
+ }
6148
+ }
6149
+ }
6150
+ }
6151
+ }
6152
+ }
6153
+ },
6154
+ "/api/csrf-token": {
6155
+ "get": {
6156
+ "summary": "Mint a short-lived CSRF token for the WebRTC SDP exchange.",
6157
+ "description": "Returned token is required as `X-CSRF-Token` on POST /api/realtime/webrtc and is bound to the calling user for 5 minutes.",
6158
+ "tags": [
6159
+ "chatty"
6160
+ ],
6161
+ "parameters": [
6162
+ {
6163
+ "name": "X-Client-Version",
6164
+ "in": "header",
6165
+ "required": false,
6166
+ "schema": {
6167
+ "type": "string",
6168
+ "example": "1.0.0"
6169
+ },
6170
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
6171
+ }
6172
+ ],
6173
+ "responses": {
6174
+ "200": {
6175
+ "description": "Fresh CSRF token.",
6176
+ "content": {
6177
+ "application/json": {
6178
+ "schema": {
6179
+ "type": "object",
6180
+ "properties": {
6181
+ "token": {
6182
+ "type": "string",
6183
+ "minLength": 1
6184
+ }
6185
+ },
6186
+ "required": [
6187
+ "token"
6188
+ ]
6189
+ }
6190
+ }
6191
+ }
6192
+ },
6193
+ "401": {
6194
+ "description": "Unauthenticated.",
6195
+ "content": {
6196
+ "application/json": {
6197
+ "schema": {
6198
+ "$ref": "#/components/schemas/ErrorEnvelope"
6199
+ }
6200
+ }
6201
+ }
6202
+ },
6203
+ "403": {
6204
+ "description": "Caller lacks the chatty-voice entitlement.",
6205
+ "content": {
6206
+ "application/json": {
6207
+ "schema": {
6208
+ "$ref": "#/components/schemas/ErrorEnvelope"
6209
+ }
6210
+ }
6211
+ }
6212
+ }
6213
+ }
6214
+ }
6215
+ },
6216
+ "/api/realtime/webrtc": {
6217
+ "post": {
6218
+ "summary": "Server-proxied OpenAI Realtime SDP exchange (desktop branch).",
6219
+ "description": "Request body is JSON `{ sdp }`; the server mints a fresh OpenAI ephemeral client_secret, forwards the raw SDP to OpenAI's WebRTC endpoint, and returns the raw SDP answer as `application/sdp` text. CSRF-protected via `X-CSRF-Token` (see GET /api/csrf-token) and rate-limited to 3 req/min/IP.",
6220
+ "tags": [
6221
+ "chatty"
6222
+ ],
6223
+ "parameters": [
6224
+ {
6225
+ "name": "X-Client-Version",
6226
+ "in": "header",
6227
+ "required": false,
6228
+ "schema": {
6229
+ "type": "string",
6230
+ "example": "1.0.0"
6231
+ },
6232
+ "description": "Semver of the calling app build. Drives the 426 / soft-upgrade handshake."
6233
+ },
6234
+ {
6235
+ "name": "X-CSRF-Token",
6236
+ "in": "header",
6237
+ "required": true,
6238
+ "schema": {
6239
+ "type": "string"
6240
+ },
6241
+ "description": "CSRF token previously minted by GET /api/csrf-token."
6242
+ }
6243
+ ],
6244
+ "requestBody": {
6245
+ "content": {
6246
+ "application/json": {
6247
+ "schema": {
6248
+ "type": "object",
6249
+ "properties": {
6250
+ "sdp": {
6251
+ "type": "string",
6252
+ "minLength": 1
6253
+ }
6254
+ },
6255
+ "required": [
6256
+ "sdp"
6257
+ ]
6258
+ }
6259
+ }
6260
+ }
6261
+ },
6262
+ "responses": {
6263
+ "200": {
6264
+ "description": "Raw OpenAI SDP answer (text body, not JSON).",
6265
+ "content": {
6266
+ "application/sdp": {
6267
+ "schema": {
6268
+ "type": "string"
6269
+ }
6270
+ }
6271
+ }
6272
+ },
6273
+ "400": {
6274
+ "description": "Invalid SDP format.",
6275
+ "content": {
6276
+ "application/json": {
6277
+ "schema": {
6278
+ "$ref": "#/components/schemas/ErrorEnvelope"
6279
+ }
6280
+ }
6281
+ }
6282
+ },
6283
+ "401": {
6284
+ "description": "Unauthenticated.",
6285
+ "content": {
6286
+ "application/json": {
6287
+ "schema": {
6288
+ "$ref": "#/components/schemas/ErrorEnvelope"
6289
+ }
6290
+ }
6291
+ }
6292
+ },
6293
+ "403": {
6294
+ "description": "Caller lacks the chatty-voice entitlement, or CSRF token invalid.",
6295
+ "content": {
6296
+ "application/json": {
6297
+ "schema": {
6298
+ "$ref": "#/components/schemas/ErrorEnvelope"
6299
+ }
6300
+ }
6301
+ }
6302
+ },
6303
+ "429": {
6304
+ "description": "Rate limit (3 req/min/IP).",
6305
+ "content": {
6306
+ "application/json": {
6307
+ "schema": {
6308
+ "$ref": "#/components/schemas/ErrorEnvelope"
6309
+ }
6310
+ }
6311
+ }
6312
+ },
6313
+ "500": {
6314
+ "description": "SDP exchange failed locally.",
6315
+ "content": {
6316
+ "application/json": {
6317
+ "schema": {
6318
+ "$ref": "#/components/schemas/ErrorEnvelope"
6319
+ }
6320
+ }
6321
+ }
6322
+ },
6323
+ "502": {
6324
+ "description": "Local failure minting the OpenAI ephemeral client_secret required to authenticate the SDP exchange. Returned as a JSON envelope generated by this server: `{ error, status? }`.",
6325
+ "content": {
6326
+ "application/json": {
6327
+ "schema": {
6328
+ "type": "object",
6329
+ "properties": {
6330
+ "error": {
6331
+ "type": "string"
6332
+ },
6333
+ "status": {
6334
+ "type": "integer"
6335
+ }
6336
+ },
6337
+ "required": [
6338
+ "error"
6339
+ ]
6340
+ }
6341
+ }
6342
+ }
6343
+ },
6344
+ "default": {
6345
+ "description": "Upstream OpenAI SDP exchange error. Pass-through: this endpoint forwards OpenAI's HTTP status verbatim (typically 4xx/5xx) and the body is OpenAI's raw error payload — NOT a JSON envelope generated by this server. Content-Type and exact shape are determined by OpenAI; clients MUST treat any non-200 response on this endpoint as opaque error text unless they explicitly detect the 502 JSON envelope above.",
6346
+ "content": {
6347
+ "application/json": {
6348
+ "schema": {}
6349
+ },
6350
+ "text/plain": {
6351
+ "schema": {
6352
+ "type": "string"
6353
+ }
6354
+ }
6355
+ }
6356
+ }
6357
+ }
6358
+ }
5141
6359
  }
5142
6360
  },
5143
6361
  "webhooks": {}