haystack-contracts 1.0.0 → 1.0.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 ADDED
@@ -0,0 +1,1380 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "paths": {
4
+ "/auth/login": {
5
+ "post": {
6
+ "description": "Authenticates a user using a globally unique email address and password. This endpoint is public and does not require a bearer token.",
7
+ "requestBody": {
8
+ "required": true,
9
+ "content": {
10
+ "application/json": {
11
+ "schema": {
12
+ "$ref": "#/components/schemas/LoginDto"
13
+ }
14
+ }
15
+ }
16
+ },
17
+ "responses": {
18
+ "200": {
19
+ "description": "Authenticated successfully and returned access/refresh tokens.",
20
+ "content": {
21
+ "application/json": {
22
+ "schema": {
23
+ "type": "object",
24
+ "required": ["success", "statusCode", "data", "timestamp"],
25
+ "properties": {
26
+ "success": {
27
+ "type": "boolean",
28
+ "example": true
29
+ },
30
+ "statusCode": {
31
+ "type": "integer",
32
+ "example": 200
33
+ },
34
+ "timestamp": {
35
+ "type": "string",
36
+ "format": "date-time",
37
+ "example": "2026-05-29T12:00:00.000Z"
38
+ },
39
+ "data": {
40
+ "$ref": "#/components/schemas/AuthResponseDto"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ },
47
+ "400": {
48
+ "description": "Validation failed. Check that all required fields are present and correctly typed.",
49
+ "content": {
50
+ "application/json": {
51
+ "example": {
52
+ "success": false,
53
+ "statusCode": 400,
54
+ "error": "Bad Request",
55
+ "message": "email must be an email"
56
+ }
57
+ }
58
+ }
59
+ },
60
+ "401": {
61
+ "description": "Authentication required. Provide a valid bearer token.",
62
+ "content": {
63
+ "application/json": {
64
+ "example": {
65
+ "success": false,
66
+ "statusCode": 401,
67
+ "error": "Unauthorized",
68
+ "message": "Unauthorized"
69
+ }
70
+ }
71
+ }
72
+ }
73
+ },
74
+ "summary": "Login with email and password",
75
+ "tags": ["auth"]
76
+ }
77
+ },
78
+ "/robots": {
79
+ "get": {
80
+ "description": "Returns all robots registered in the configured tenant, ordered by online status and name. Each record includes the robot name, tenant, fleet assignment, online status, subscription status, and last-seen timestamp. Any authenticated user can access this endpoint.",
81
+ "responses": {
82
+ "200": {
83
+ "description": "List of robots in the current tenant.",
84
+ "content": {
85
+ "application/json": {
86
+ "schema": {
87
+ "type": "object",
88
+ "required": ["success", "statusCode", "data", "timestamp"],
89
+ "properties": {
90
+ "success": {
91
+ "type": "boolean",
92
+ "example": true
93
+ },
94
+ "statusCode": {
95
+ "type": "integer",
96
+ "example": 200
97
+ },
98
+ "timestamp": {
99
+ "type": "string",
100
+ "format": "date-time",
101
+ "example": "2026-05-29T12:00:00.000Z"
102
+ },
103
+ "data": {
104
+ "type": "array",
105
+ "items": {
106
+ "$ref": "#/components/schemas/RobotListItemDto"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ },
114
+ "401": {
115
+ "description": "Authentication required. Provide a valid bearer token.",
116
+ "content": {
117
+ "application/json": {
118
+ "example": {
119
+ "success": false,
120
+ "statusCode": 401,
121
+ "error": "Unauthorized",
122
+ "message": "Unauthorized"
123
+ }
124
+ }
125
+ }
126
+ }
127
+ },
128
+ "summary": "List all robots",
129
+ "tags": ["robots"]
130
+ }
131
+ },
132
+ "/robots/{id}": {
133
+ "get": {
134
+ "description": "Returns the full record for a single robot, including its fleet assignment, online state, and timestamps.",
135
+ "parameters": [
136
+ {
137
+ "name": "id",
138
+ "required": true,
139
+ "in": "path",
140
+ "description": "UUID of the robot.",
141
+ "schema": {
142
+ "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
143
+ "type": "string"
144
+ }
145
+ }
146
+ ],
147
+ "responses": {
148
+ "200": {
149
+ "description": "Robot record.",
150
+ "content": {
151
+ "application/json": {
152
+ "schema": {
153
+ "type": "object",
154
+ "required": ["success", "statusCode", "data", "timestamp"],
155
+ "properties": {
156
+ "success": {
157
+ "type": "boolean",
158
+ "example": true
159
+ },
160
+ "statusCode": {
161
+ "type": "integer",
162
+ "example": 200
163
+ },
164
+ "timestamp": {
165
+ "type": "string",
166
+ "format": "date-time",
167
+ "example": "2026-05-29T12:00:00.000Z"
168
+ },
169
+ "data": {
170
+ "$ref": "#/components/schemas/RobotListItemDto"
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ },
177
+ "400": {
178
+ "description": "Validation failed. Check that all required fields are present and correctly typed.",
179
+ "content": {
180
+ "application/json": {
181
+ "example": {
182
+ "success": false,
183
+ "statusCode": 400,
184
+ "error": "Bad Request",
185
+ "message": "email must be an email"
186
+ }
187
+ }
188
+ }
189
+ },
190
+ "401": {
191
+ "description": "Authentication required. Provide a valid bearer token.",
192
+ "content": {
193
+ "application/json": {
194
+ "example": {
195
+ "success": false,
196
+ "statusCode": 401,
197
+ "error": "Unauthorized",
198
+ "message": "Unauthorized"
199
+ }
200
+ }
201
+ }
202
+ },
203
+ "404": {
204
+ "description": "Robot not found in the current tenant.",
205
+ "content": {
206
+ "application/json": {
207
+ "example": {
208
+ "success": false,
209
+ "statusCode": 404,
210
+ "error": "Not Found",
211
+ "message": "Robot not found"
212
+ }
213
+ }
214
+ }
215
+ }
216
+ },
217
+ "summary": "Get a robot by ID",
218
+ "tags": ["robots"]
219
+ }
220
+ },
221
+ "/metrics/types": {
222
+ "get": {
223
+ "description": "Returns the full registry of metric types from the analytics store. Each entry includes an integer id, a human-readable name, and a description. Any authenticated user can access this endpoint.",
224
+ "responses": {
225
+ "200": {
226
+ "description": "List of all registered metric types.",
227
+ "content": {
228
+ "application/json": {
229
+ "schema": {
230
+ "type": "object",
231
+ "required": ["success", "statusCode", "data", "timestamp"],
232
+ "properties": {
233
+ "success": {
234
+ "type": "boolean",
235
+ "example": true
236
+ },
237
+ "statusCode": {
238
+ "type": "integer",
239
+ "example": 200
240
+ },
241
+ "timestamp": {
242
+ "type": "string",
243
+ "format": "date-time",
244
+ "example": "2026-05-29T12:00:00.000Z"
245
+ },
246
+ "data": {
247
+ "type": "array",
248
+ "items": {
249
+ "$ref": "#/components/schemas/MetricTypeResponseDto"
250
+ }
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+ },
257
+ "401": {
258
+ "description": "Authentication required. Provide a valid bearer token.",
259
+ "content": {
260
+ "application/json": {
261
+ "example": {
262
+ "success": false,
263
+ "statusCode": 401,
264
+ "error": "Unauthorized",
265
+ "message": "Unauthorized"
266
+ }
267
+ }
268
+ }
269
+ }
270
+ },
271
+ "summary": "List all metric types",
272
+ "tags": ["metrics"]
273
+ }
274
+ },
275
+ "/metrics/robot-values/{robotId}": {
276
+ "get": {
277
+ "description": "Returns the most recent reading for every metric type collected for the specified robot from the analytics (OLAP) store. Pass `metricType` as a query param to restrict results to a single type.",
278
+ "parameters": [
279
+ {
280
+ "name": "robotId",
281
+ "required": true,
282
+ "in": "path",
283
+ "description": "UUID of the robot.",
284
+ "schema": {
285
+ "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
286
+ "type": "string"
287
+ }
288
+ },
289
+ {
290
+ "name": "metricType",
291
+ "required": false,
292
+ "in": "query",
293
+ "description": "Integer id of a specific metric type to filter by.",
294
+ "schema": {
295
+ "example": 1,
296
+ "type": "number"
297
+ }
298
+ }
299
+ ],
300
+ "responses": {
301
+ "200": {
302
+ "description": "Latest OLAP metric readings for the robot.",
303
+ "content": {
304
+ "application/json": {
305
+ "schema": {
306
+ "type": "object",
307
+ "required": ["success", "statusCode", "data", "timestamp"],
308
+ "properties": {
309
+ "success": {
310
+ "type": "boolean",
311
+ "example": true
312
+ },
313
+ "statusCode": {
314
+ "type": "integer",
315
+ "example": 200
316
+ },
317
+ "timestamp": {
318
+ "type": "string",
319
+ "format": "date-time",
320
+ "example": "2026-05-29T12:00:00.000Z"
321
+ },
322
+ "data": {
323
+ "type": "array",
324
+ "items": {
325
+ "$ref": "#/components/schemas/RobotMetricLiveDto"
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ }
332
+ },
333
+ "400": {
334
+ "description": "Validation failed. Check that all required fields are present and correctly typed.",
335
+ "content": {
336
+ "application/json": {
337
+ "example": {
338
+ "success": false,
339
+ "statusCode": 400,
340
+ "error": "Bad Request",
341
+ "message": "email must be an email"
342
+ }
343
+ }
344
+ }
345
+ },
346
+ "401": {
347
+ "description": "Authentication required. Provide a valid bearer token.",
348
+ "content": {
349
+ "application/json": {
350
+ "example": {
351
+ "success": false,
352
+ "statusCode": 401,
353
+ "error": "Unauthorized",
354
+ "message": "Unauthorized"
355
+ }
356
+ }
357
+ }
358
+ }
359
+ },
360
+ "summary": "List latest metric values for a robot",
361
+ "tags": ["metrics"]
362
+ }
363
+ },
364
+ "/disinfection/robots": {
365
+ "get": {
366
+ "description": "Returns aggregated disinfection statistics for every robot in the current tenant. Includes bot name, facility name, room count, total duration, and success/partial/failed job counts.",
367
+ "responses": {
368
+ "200": {
369
+ "description": "Per-robot disinfection statistics for the current tenant.",
370
+ "content": {
371
+ "application/json": {
372
+ "schema": {
373
+ "type": "object",
374
+ "required": ["success", "statusCode", "data", "timestamp"],
375
+ "properties": {
376
+ "success": {
377
+ "type": "boolean",
378
+ "example": true
379
+ },
380
+ "statusCode": {
381
+ "type": "integer",
382
+ "example": 200
383
+ },
384
+ "timestamp": {
385
+ "type": "string",
386
+ "format": "date-time",
387
+ "example": "2026-05-29T12:00:00.000Z"
388
+ },
389
+ "data": {
390
+ "type": "array",
391
+ "items": {
392
+ "$ref": "#/components/schemas/RobotDisinfectionStatsDto"
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+ }
399
+ },
400
+ "401": {
401
+ "description": "Authentication required. Provide a valid bearer token.",
402
+ "content": {
403
+ "application/json": {
404
+ "example": {
405
+ "success": false,
406
+ "statusCode": 401,
407
+ "error": "Unauthorized",
408
+ "message": "Unauthorized"
409
+ }
410
+ }
411
+ }
412
+ }
413
+ },
414
+ "summary": "Get per-robot disinfection stats",
415
+ "tags": ["disinfection"]
416
+ }
417
+ },
418
+ "/disinfection/ongoing-jobs": {
419
+ "get": {
420
+ "description": "Returns currently active disinfection jobs from OLTP. TODO: not yet implemented — returns an empty list until the OLTP query is defined.",
421
+ "responses": {
422
+ "200": {
423
+ "description": "List of ongoing disinfection jobs (currently empty).",
424
+ "content": {
425
+ "application/json": {
426
+ "schema": {
427
+ "type": "object",
428
+ "required": ["success", "statusCode", "data", "timestamp"],
429
+ "properties": {
430
+ "success": {
431
+ "type": "boolean",
432
+ "example": true
433
+ },
434
+ "statusCode": {
435
+ "type": "integer",
436
+ "example": 200
437
+ },
438
+ "timestamp": {
439
+ "type": "string",
440
+ "format": "date-time",
441
+ "example": "2026-05-29T12:00:00.000Z"
442
+ },
443
+ "data": {
444
+ "type": "array",
445
+ "items": {
446
+ "$ref": "#/components/schemas/DisinfectionJobDto"
447
+ }
448
+ }
449
+ }
450
+ }
451
+ }
452
+ }
453
+ },
454
+ "401": {
455
+ "description": "Authentication required. Provide a valid bearer token.",
456
+ "content": {
457
+ "application/json": {
458
+ "example": {
459
+ "success": false,
460
+ "statusCode": 401,
461
+ "error": "Unauthorized",
462
+ "message": "Unauthorized"
463
+ }
464
+ }
465
+ }
466
+ }
467
+ },
468
+ "summary": "List ongoing disinfection jobs",
469
+ "tags": ["disinfection"]
470
+ }
471
+ },
472
+ "/disinfection/jobs": {
473
+ "get": {
474
+ "description": "Returns completed disinfection jobs from OLAP. Optionally filter by status (passed, partial, failed); omit status or use \"all\" for no status filter. Haystack tenant users see jobs across all tenants; other tenants are scoped to their own tenant. Optionally filter by robotId.",
475
+ "parameters": [
476
+ {
477
+ "name": "status",
478
+ "required": false,
479
+ "in": "query",
480
+ "description": "Job outcome filter. Defaults to \"all\" (no status filtering) when omitted.",
481
+ "schema": {
482
+ "enum": ["passed", "partial", "failed", "all"],
483
+ "type": "string"
484
+ }
485
+ },
486
+ {
487
+ "name": "robotId",
488
+ "required": false,
489
+ "in": "query",
490
+ "description": "UUID of a specific robot to filter by.",
491
+ "schema": {
492
+ "type": "string"
493
+ }
494
+ }
495
+ ],
496
+ "responses": {
497
+ "200": {
498
+ "description": "List of disinfection jobs matching the filters.",
499
+ "content": {
500
+ "application/json": {
501
+ "schema": {
502
+ "type": "object",
503
+ "required": ["success", "statusCode", "data", "timestamp"],
504
+ "properties": {
505
+ "success": {
506
+ "type": "boolean",
507
+ "example": true
508
+ },
509
+ "statusCode": {
510
+ "type": "integer",
511
+ "example": 200
512
+ },
513
+ "timestamp": {
514
+ "type": "string",
515
+ "format": "date-time",
516
+ "example": "2026-05-29T12:00:00.000Z"
517
+ },
518
+ "data": {
519
+ "type": "array",
520
+ "items": {
521
+ "$ref": "#/components/schemas/DisinfectionJobDto"
522
+ }
523
+ }
524
+ }
525
+ }
526
+ }
527
+ }
528
+ },
529
+ "400": {
530
+ "description": "Validation failed. Check that all required fields are present and correctly typed.",
531
+ "content": {
532
+ "application/json": {
533
+ "example": {
534
+ "success": false,
535
+ "statusCode": 400,
536
+ "error": "Bad Request",
537
+ "message": "email must be an email"
538
+ }
539
+ }
540
+ }
541
+ },
542
+ "401": {
543
+ "description": "Authentication required. Provide a valid bearer token.",
544
+ "content": {
545
+ "application/json": {
546
+ "example": {
547
+ "success": false,
548
+ "statusCode": 401,
549
+ "error": "Unauthorized",
550
+ "message": "Unauthorized"
551
+ }
552
+ }
553
+ }
554
+ }
555
+ },
556
+ "summary": "List disinfection jobs",
557
+ "tags": ["disinfection"]
558
+ }
559
+ },
560
+ "/analytics-agg/robots-count": {
561
+ "get": {
562
+ "description": "Returns total, online, offline, AI-enabled, and SW-update-pending robot counts. Haystack tenant users see aggregates across all tenants; other tenants are scoped to their own tenant.",
563
+ "responses": {
564
+ "200": {
565
+ "description": "Robot count statistics wrapped in AnalyticsResponse with timestamp and status.",
566
+ "content": {
567
+ "application/json": {
568
+ "schema": {
569
+ "type": "object",
570
+ "required": ["success", "statusCode", "data", "timestamp"],
571
+ "properties": {
572
+ "success": {
573
+ "type": "boolean",
574
+ "example": true
575
+ },
576
+ "statusCode": {
577
+ "type": "integer",
578
+ "example": 200
579
+ },
580
+ "timestamp": {
581
+ "type": "string",
582
+ "format": "date-time",
583
+ "example": "2026-05-29T12:00:00.000Z"
584
+ },
585
+ "data": {
586
+ "$ref": "#/components/schemas/RobotsCountAnalyticsDataDto"
587
+ }
588
+ }
589
+ }
590
+ }
591
+ }
592
+ },
593
+ "401": {
594
+ "description": "Authentication required. Provide a valid bearer token.",
595
+ "content": {
596
+ "application/json": {
597
+ "example": {
598
+ "success": false,
599
+ "statusCode": 401,
600
+ "error": "Unauthorized",
601
+ "message": "Unauthorized"
602
+ }
603
+ }
604
+ }
605
+ }
606
+ },
607
+ "summary": "Get robot count statistics",
608
+ "tags": ["analytics-agg"]
609
+ }
610
+ },
611
+ "/analytics-agg/disinfection-count": {
612
+ "get": {
613
+ "description": "Returns top performing bot by total disinfection duration and aggregated job stats (total runtime, total successful, total partial jobs). Haystack tenant users see aggregates across all tenants; other tenants are scoped to their own tenant.",
614
+ "responses": {
615
+ "200": {
616
+ "description": "Disinfection count statistics wrapped in AnalyticsResponse with timestamp and status.",
617
+ "content": {
618
+ "application/json": {
619
+ "schema": {
620
+ "type": "object",
621
+ "required": ["success", "statusCode", "data", "timestamp"],
622
+ "properties": {
623
+ "success": {
624
+ "type": "boolean",
625
+ "example": true
626
+ },
627
+ "statusCode": {
628
+ "type": "integer",
629
+ "example": 200
630
+ },
631
+ "timestamp": {
632
+ "type": "string",
633
+ "format": "date-time",
634
+ "example": "2026-05-29T12:00:00.000Z"
635
+ },
636
+ "data": {
637
+ "$ref": "#/components/schemas/DisinfectionCountAnalyticsDataDto"
638
+ }
639
+ }
640
+ }
641
+ }
642
+ }
643
+ },
644
+ "401": {
645
+ "description": "Authentication required. Provide a valid bearer token.",
646
+ "content": {
647
+ "application/json": {
648
+ "example": {
649
+ "success": false,
650
+ "statusCode": 401,
651
+ "error": "Unauthorized",
652
+ "message": "Unauthorized"
653
+ }
654
+ }
655
+ }
656
+ }
657
+ },
658
+ "summary": "Get disinfection job aggregate statistics",
659
+ "tags": ["analytics-agg"]
660
+ }
661
+ },
662
+ "/analytics-agg/disinfection-operator": {
663
+ "get": {
664
+ "description": "Returns operators ranked by number of successful/partial disinfection jobs. Haystack tenant users see data across all tenants; other tenants are scoped to their own tenant.",
665
+ "responses": {
666
+ "200": {
667
+ "description": "List of operators ranked by total jobs wrapped in AnalyticsResponse with timestamp and status.",
668
+ "content": {
669
+ "application/json": {
670
+ "schema": {
671
+ "type": "object",
672
+ "required": ["success", "statusCode", "data", "timestamp"],
673
+ "properties": {
674
+ "success": {
675
+ "type": "boolean",
676
+ "example": true
677
+ },
678
+ "statusCode": {
679
+ "type": "integer",
680
+ "example": 200
681
+ },
682
+ "timestamp": {
683
+ "type": "string",
684
+ "format": "date-time",
685
+ "example": "2026-05-29T12:00:00.000Z"
686
+ },
687
+ "data": {
688
+ "$ref": "#/components/schemas/DisinfectionOperatorsAnalyticsDataDto"
689
+ }
690
+ }
691
+ }
692
+ }
693
+ }
694
+ },
695
+ "401": {
696
+ "description": "Authentication required. Provide a valid bearer token.",
697
+ "content": {
698
+ "application/json": {
699
+ "example": {
700
+ "success": false,
701
+ "statusCode": 401,
702
+ "error": "Unauthorized",
703
+ "message": "Unauthorized"
704
+ }
705
+ }
706
+ }
707
+ }
708
+ },
709
+ "summary": "Get operator rankings by disinfection jobs",
710
+ "tags": ["analytics-agg"]
711
+ }
712
+ },
713
+ "/analytics-agg/disinfection-status": {
714
+ "get": {
715
+ "description": "Returns status-wise counts of disinfection jobs. Haystack tenant users see aggregates across all tenants; other tenants are scoped to their own tenant.",
716
+ "responses": {
717
+ "200": {
718
+ "description": "Status-wise job counts wrapped in AnalyticsResponse with timestamp and status.",
719
+ "content": {
720
+ "application/json": {
721
+ "schema": {
722
+ "type": "object",
723
+ "required": ["success", "statusCode", "data", "timestamp"],
724
+ "properties": {
725
+ "success": {
726
+ "type": "boolean",
727
+ "example": true
728
+ },
729
+ "statusCode": {
730
+ "type": "integer",
731
+ "example": 200
732
+ },
733
+ "timestamp": {
734
+ "type": "string",
735
+ "format": "date-time",
736
+ "example": "2026-05-29T12:00:00.000Z"
737
+ },
738
+ "data": {
739
+ "$ref": "#/components/schemas/DisinfectionStatusAnalyticsDataDto"
740
+ }
741
+ }
742
+ }
743
+ }
744
+ }
745
+ },
746
+ "401": {
747
+ "description": "Authentication required. Provide a valid bearer token.",
748
+ "content": {
749
+ "application/json": {
750
+ "example": {
751
+ "success": false,
752
+ "statusCode": 401,
753
+ "error": "Unauthorized",
754
+ "message": "Unauthorized"
755
+ }
756
+ }
757
+ }
758
+ }
759
+ },
760
+ "summary": "Get disinfection job status distribution",
761
+ "tags": ["analytics-agg"]
762
+ }
763
+ }
764
+ },
765
+ "info": {
766
+ "title": "Haystack Robotics API",
767
+ "version": "1.0.0",
768
+ "description": "Customer-facing REST API for Haystack Robotics integrations.\n\nBase path: `/api/v1`\n\nAuthentication:\n- `POST /auth/login` — obtain access and refresh tokens.\n- Protected routes require `Authorization: Bearer <access_token>`.\n\nResponse envelope (success):\n`{ \"success\": true, \"statusCode\": 200, \"data\": { ... }, \"timestamp\": \"ISO-8601\" }`\n\nResponse envelope (error):\n`{ \"success\": false, \"statusCode\": 4xx, \"error\": \"...\", \"message\": \"...\" }`",
769
+ "contact": {
770
+ "name": "Haystack Robotics",
771
+ "url": "https://docs.haystack-robotics.com",
772
+ "email": "support@haystackrobotics.com"
773
+ }
774
+ },
775
+ "tags": [
776
+ {
777
+ "name": "analytics-agg",
778
+ "description": "Fleet-wide analytics summaries from the analytics store (robot counts, disinfection stats, operators, job status)."
779
+ },
780
+ {
781
+ "name": "auth",
782
+ "description": "Authentication. Use login to obtain tokens; protected routes require a bearer token."
783
+ },
784
+ {
785
+ "name": "disinfection",
786
+ "description": "UVC disinfection analytics — per-robot aggregates and job history."
787
+ },
788
+ {
789
+ "name": "metrics",
790
+ "description": "Metric type catalog and latest per-robot metric readings."
791
+ },
792
+ {
793
+ "name": "robots",
794
+ "description": "Robot inventory for the authenticated tenant, including online status and fleet assignment."
795
+ }
796
+ ],
797
+ "servers": [
798
+ {
799
+ "url": "https://api.haystackrobotics.com/api/v1",
800
+ "description": "Production"
801
+ }
802
+ ],
803
+ "components": {
804
+ "schemas": {
805
+ "AuthResponseDto": {
806
+ "type": "object",
807
+ "properties": {
808
+ "accessToken": {
809
+ "type": "string",
810
+ "description": "Short-lived JWT access token (15 min).",
811
+ "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.access-token-example.signature"
812
+ },
813
+ "refreshToken": {
814
+ "type": "string",
815
+ "description": "Long-lived refresh token (7 days).",
816
+ "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.refresh-token-example.signature"
817
+ },
818
+ "expiresIn": {
819
+ "type": "number",
820
+ "description": "Access token lifetime in seconds.",
821
+ "example": 900
822
+ },
823
+ "user": {
824
+ "description": "Sanitized authenticated user snapshot returned with the token response.",
825
+ "allOf": [
826
+ {
827
+ "$ref": "#/components/schemas/PublicUserDto"
828
+ }
829
+ ]
830
+ }
831
+ },
832
+ "required": ["accessToken", "refreshToken", "expiresIn", "user"]
833
+ },
834
+ "DisinfectionCountAnalyticsDataDto": {
835
+ "type": "object",
836
+ "properties": {
837
+ "data": {
838
+ "$ref": "#/components/schemas/DisinfectionCountResponseDto"
839
+ },
840
+ "timestamp": {
841
+ "type": "string",
842
+ "example": "2026-05-29T12:00:00.000Z"
843
+ },
844
+ "status": {
845
+ "type": "string",
846
+ "enum": ["success", "error"],
847
+ "example": "success"
848
+ }
849
+ },
850
+ "required": ["data", "timestamp", "status"]
851
+ },
852
+ "DisinfectionCountResponseDto": {
853
+ "type": "object",
854
+ "properties": {
855
+ "topBotName": {
856
+ "type": "string",
857
+ "example": "Warehouse Bot Alpha"
858
+ },
859
+ "topBotId": {
860
+ "type": "string",
861
+ "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
862
+ },
863
+ "totalRuntime": {
864
+ "type": "number",
865
+ "example": 125000
866
+ },
867
+ "totalSuccessful": {
868
+ "type": "number",
869
+ "example": 320
870
+ },
871
+ "totalPartial": {
872
+ "type": "number",
873
+ "example": 45
874
+ }
875
+ },
876
+ "required": [
877
+ "topBotName",
878
+ "topBotId",
879
+ "totalRuntime",
880
+ "totalSuccessful",
881
+ "totalPartial"
882
+ ]
883
+ },
884
+ "DisinfectionJobDto": {
885
+ "type": "object",
886
+ "properties": {
887
+ "robotName": {
888
+ "type": "string",
889
+ "example": "Warehouse Bot Alpha"
890
+ },
891
+ "startTime": {
892
+ "type": "string",
893
+ "example": "2026-05-28T14:00:00.000Z"
894
+ },
895
+ "endTime": {
896
+ "type": "string",
897
+ "example": "2026-05-28T14:45:00.000Z",
898
+ "nullable": true
899
+ },
900
+ "durationSeconds": {
901
+ "type": "number",
902
+ "example": 2700,
903
+ "nullable": true
904
+ },
905
+ "operatorName": {
906
+ "type": "string",
907
+ "example": "Alice Smith"
908
+ },
909
+ "roomName": {
910
+ "type": "string",
911
+ "example": "OR-3"
912
+ },
913
+ "disinfectMode": {
914
+ "type": "string",
915
+ "example": "AUTO"
916
+ },
917
+ "level": {
918
+ "type": "string",
919
+ "example": "HIGH"
920
+ },
921
+ "status": {
922
+ "type": "string",
923
+ "example": "COVERAGE_DONE"
924
+ }
925
+ },
926
+ "required": [
927
+ "robotName",
928
+ "startTime",
929
+ "endTime",
930
+ "durationSeconds",
931
+ "operatorName",
932
+ "roomName",
933
+ "disinfectMode",
934
+ "level",
935
+ "status"
936
+ ]
937
+ },
938
+ "DisinfectionOperatorResponseDto": {
939
+ "type": "object",
940
+ "properties": {
941
+ "operatorName": {
942
+ "type": "string",
943
+ "example": "Alice Smith"
944
+ },
945
+ "totalJobs": {
946
+ "type": "number",
947
+ "example": 128
948
+ }
949
+ },
950
+ "required": ["operatorName", "totalJobs"]
951
+ },
952
+ "DisinfectionOperatorsAnalyticsDataDto": {
953
+ "type": "object",
954
+ "properties": {
955
+ "data": {
956
+ "type": "array",
957
+ "items": {
958
+ "$ref": "#/components/schemas/DisinfectionOperatorResponseDto"
959
+ }
960
+ },
961
+ "timestamp": {
962
+ "type": "string",
963
+ "example": "2026-05-29T12:00:00.000Z"
964
+ },
965
+ "status": {
966
+ "type": "string",
967
+ "enum": ["success", "error"],
968
+ "example": "success"
969
+ }
970
+ },
971
+ "required": ["data", "timestamp", "status"]
972
+ },
973
+ "DisinfectionStatusAnalyticsDataDto": {
974
+ "type": "object",
975
+ "properties": {
976
+ "data": {
977
+ "type": "array",
978
+ "items": {
979
+ "$ref": "#/components/schemas/DisinfectionStatusResponseDto"
980
+ }
981
+ },
982
+ "timestamp": {
983
+ "type": "string",
984
+ "example": "2026-05-29T12:00:00.000Z"
985
+ },
986
+ "status": {
987
+ "type": "string",
988
+ "enum": ["success", "error"],
989
+ "example": "success"
990
+ }
991
+ },
992
+ "required": ["data", "timestamp", "status"]
993
+ },
994
+ "DisinfectionStatusResponseDto": {
995
+ "type": "object",
996
+ "properties": {
997
+ "status": {
998
+ "type": "string",
999
+ "example": "COVERAGE_DONE"
1000
+ },
1001
+ "count": {
1002
+ "type": "number",
1003
+ "example": 500
1004
+ }
1005
+ },
1006
+ "required": ["status", "count"]
1007
+ },
1008
+ "LoginDto": {
1009
+ "type": "object",
1010
+ "properties": {
1011
+ "email": {
1012
+ "type": "string",
1013
+ "description": "Registered email address.",
1014
+ "example": "superadmin@haystackrobotics.com"
1015
+ },
1016
+ "password": {
1017
+ "type": "string",
1018
+ "description": "Account password (minimum 8 characters).",
1019
+ "example": "SuperAdmin123!",
1020
+ "minLength": 8
1021
+ }
1022
+ },
1023
+ "required": ["email", "password"]
1024
+ },
1025
+ "MetricTypeResponseDto": {
1026
+ "type": "object",
1027
+ "properties": {
1028
+ "metricType": {
1029
+ "type": "number",
1030
+ "description": "Integer id of the metric type.",
1031
+ "example": 1
1032
+ },
1033
+ "metricName": {
1034
+ "type": "string",
1035
+ "description": "Human-readable metric name.",
1036
+ "example": "Cpu"
1037
+ },
1038
+ "description": {
1039
+ "type": "string",
1040
+ "description": "What the metric measures.",
1041
+ "example": "CPU utilisation percentage"
1042
+ },
1043
+ "dataType": {
1044
+ "type": "string",
1045
+ "enum": ["numeric", "string"],
1046
+ "example": "numeric"
1047
+ },
1048
+ "sourceType": {
1049
+ "type": "string",
1050
+ "enum": ["non_stream", "stream"],
1051
+ "example": "stream"
1052
+ }
1053
+ },
1054
+ "required": ["metricType", "metricName", "description", "dataType"]
1055
+ },
1056
+ "PublicUserDto": {
1057
+ "type": "object",
1058
+ "properties": {
1059
+ "id": {
1060
+ "type": "string",
1061
+ "example": "c3d4e5f6-a1b2-3456-abcd-789012345678"
1062
+ },
1063
+ "email": {
1064
+ "type": "string",
1065
+ "example": "admin@haystackrobotics.com"
1066
+ },
1067
+ "firstName": {
1068
+ "type": "string",
1069
+ "example": "Haystack"
1070
+ },
1071
+ "lastName": {
1072
+ "type": "string",
1073
+ "example": "Admin"
1074
+ },
1075
+ "isActive": {
1076
+ "type": "boolean",
1077
+ "example": true
1078
+ },
1079
+ "tenantId": {
1080
+ "type": "string",
1081
+ "example": "11111111-1111-4111-8111-111111111111"
1082
+ },
1083
+ "workspaceId": {
1084
+ "type": "string",
1085
+ "nullable": true
1086
+ },
1087
+ "facilityId": {
1088
+ "type": "string",
1089
+ "nullable": true
1090
+ },
1091
+ "fleetId": {
1092
+ "type": "string",
1093
+ "nullable": true
1094
+ },
1095
+ "role": {
1096
+ "type": "string",
1097
+ "enum": [
1098
+ "TENANT_ADMIN",
1099
+ "WORKSPACE_ADMIN",
1100
+ "FACILITY_ADMIN",
1101
+ "FLEET_OPERATOR",
1102
+ "FLEET_VIEWER"
1103
+ ],
1104
+ "nullable": true
1105
+ },
1106
+ "scopeType": {
1107
+ "type": "string",
1108
+ "enum": ["tenant", "workspace", "facility", "fleet"],
1109
+ "nullable": true
1110
+ },
1111
+ "scopeId": {
1112
+ "type": "string",
1113
+ "nullable": true
1114
+ },
1115
+ "createdAt": {
1116
+ "format": "date-time",
1117
+ "type": "string",
1118
+ "example": "2026-01-15T08:00:00.000Z"
1119
+ },
1120
+ "updatedAt": {
1121
+ "format": "date-time",
1122
+ "type": "string",
1123
+ "example": "2026-05-20T10:00:00.000Z"
1124
+ }
1125
+ },
1126
+ "required": [
1127
+ "id",
1128
+ "email",
1129
+ "firstName",
1130
+ "lastName",
1131
+ "isActive",
1132
+ "tenantId",
1133
+ "createdAt",
1134
+ "updatedAt"
1135
+ ]
1136
+ },
1137
+ "RobotDisinfectionStatsDto": {
1138
+ "type": "object",
1139
+ "properties": {
1140
+ "robotId": {
1141
+ "type": "string",
1142
+ "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
1143
+ },
1144
+ "roomCount": {
1145
+ "type": "number",
1146
+ "example": 12
1147
+ },
1148
+ "totalDurationSeconds": {
1149
+ "type": "number",
1150
+ "example": 86400
1151
+ },
1152
+ "successJobs": {
1153
+ "type": "number",
1154
+ "example": 45
1155
+ },
1156
+ "partialSuccessJobs": {
1157
+ "type": "number",
1158
+ "example": 8
1159
+ },
1160
+ "failedJobs": {
1161
+ "type": "number",
1162
+ "example": 3
1163
+ },
1164
+ "botName": {
1165
+ "type": "string",
1166
+ "example": "Warehouse Bot Alpha",
1167
+ "nullable": true
1168
+ },
1169
+ "facilityName": {
1170
+ "type": "string",
1171
+ "example": "Warehouse Operations",
1172
+ "nullable": true
1173
+ }
1174
+ },
1175
+ "required": [
1176
+ "robotId",
1177
+ "roomCount",
1178
+ "totalDurationSeconds",
1179
+ "successJobs",
1180
+ "partialSuccessJobs",
1181
+ "failedJobs",
1182
+ "botName",
1183
+ "facilityName"
1184
+ ]
1185
+ },
1186
+ "RobotListItemDto": {
1187
+ "type": "object",
1188
+ "properties": {
1189
+ "id": {
1190
+ "type": "string",
1191
+ "description": "Unique identifier of the robot (UUID).",
1192
+ "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
1193
+ },
1194
+ "tenantId": {
1195
+ "type": "string",
1196
+ "description": "UUID of the tenant that owns the robot.",
1197
+ "example": "11111111-1111-4111-8111-111111111111"
1198
+ },
1199
+ "tenantName": {
1200
+ "type": "string",
1201
+ "description": "Name of the tenant that owns the robot.",
1202
+ "example": "Haystack Robotics"
1203
+ },
1204
+ "name": {
1205
+ "type": "string",
1206
+ "description": "Human-readable display name for the robot.",
1207
+ "example": "Warehouse Bot Alpha"
1208
+ },
1209
+ "fleetId": {
1210
+ "type": "string",
1211
+ "description": "UUID of the fleet this robot is assigned to.",
1212
+ "example": "f1000000-0000-4000-8000-000000000001",
1213
+ "nullable": true
1214
+ },
1215
+ "fleetName": {
1216
+ "type": "string",
1217
+ "description": "Name of the fleet this robot is assigned to.",
1218
+ "example": "Warehouse Fleet A"
1219
+ },
1220
+ "isOnline": {
1221
+ "type": "boolean",
1222
+ "description": "Whether the robot is currently connected and transmitting data.",
1223
+ "example": true
1224
+ },
1225
+ "isSubscribed": {
1226
+ "type": "boolean",
1227
+ "description": "Whether the robot is subscribed for telemetry processing.",
1228
+ "example": true
1229
+ },
1230
+ "lastTimeSeen": {
1231
+ "type": "string",
1232
+ "description": "ISO-8601 timestamp of the last confirmed contact with the robot.",
1233
+ "example": "2026-03-07T18:30:00.000Z"
1234
+ },
1235
+ "createdAt": {
1236
+ "type": "string",
1237
+ "description": "When the robot record was created (ISO-8601).",
1238
+ "example": "2026-03-01T10:00:00.000Z"
1239
+ },
1240
+ "updatedAt": {
1241
+ "type": "string",
1242
+ "description": "When the robot record was last updated (ISO-8601).",
1243
+ "example": "2026-03-07T18:30:00.000Z"
1244
+ },
1245
+ "cpu": {
1246
+ "type": "string",
1247
+ "description": "Latest CPU usage reading (percent string), or null.",
1248
+ "example": "87.733",
1249
+ "nullable": true
1250
+ },
1251
+ "ram": {
1252
+ "type": "string",
1253
+ "description": "Latest RAM usage reading (percent string), or null.",
1254
+ "example": "77.33",
1255
+ "nullable": true
1256
+ },
1257
+ "disk": {
1258
+ "type": "string",
1259
+ "description": "Latest Disk usage reading (percent string), or null.",
1260
+ "example": "93",
1261
+ "nullable": true
1262
+ },
1263
+ "batteryPercent": {
1264
+ "type": "number",
1265
+ "description": "Battery charge level (0–100), or null if not yet streamed.",
1266
+ "example": 90,
1267
+ "nullable": true
1268
+ },
1269
+ "mode": {
1270
+ "type": "string",
1271
+ "description": "Current robot operating mode, or null if not yet streamed.",
1272
+ "example": "DISINFECT",
1273
+ "nullable": true
1274
+ },
1275
+ "emergency": {
1276
+ "type": "boolean",
1277
+ "description": "Whether the emergency button is active, or null if not yet streamed.",
1278
+ "example": false,
1279
+ "nullable": true
1280
+ },
1281
+ "lampLifeHours": {
1282
+ "type": "number",
1283
+ "description": "Remaining lamp life in hours, or null if not yet streamed.",
1284
+ "example": 9000,
1285
+ "nullable": true
1286
+ },
1287
+ "disinfectionRunning": {
1288
+ "type": "boolean",
1289
+ "description": "Whether a disinfection job is currently running, or null if not yet streamed.",
1290
+ "example": true,
1291
+ "nullable": true
1292
+ },
1293
+ "lastStreamed": {
1294
+ "type": "string",
1295
+ "description": "ISO-8601 timestamp of the last received status stream update, or null.",
1296
+ "example": "2026-05-20T10:00:00.000Z",
1297
+ "nullable": true
1298
+ }
1299
+ },
1300
+ "required": ["id", "tenantId", "name", "isSubscribed"]
1301
+ },
1302
+ "RobotMetricLiveDto": {
1303
+ "type": "object",
1304
+ "properties": {
1305
+ "metricId": {
1306
+ "type": "number",
1307
+ "description": "Integer id of the metric type.",
1308
+ "example": 1
1309
+ },
1310
+ "metricName": {
1311
+ "type": "string",
1312
+ "description": "Human-readable metric name.",
1313
+ "example": "Cpu"
1314
+ },
1315
+ "metricValue": {
1316
+ "type": "number",
1317
+ "description": "Latest recorded value.",
1318
+ "example": 42.5
1319
+ },
1320
+ "lastSyncTime": {
1321
+ "type": "object",
1322
+ "description": "ISO-8601 timestamp of the most recent reading.",
1323
+ "example": "2026-05-29T11:58:00.000Z"
1324
+ }
1325
+ },
1326
+ "required": ["metricId", "metricName", "metricValue", "lastSyncTime"]
1327
+ },
1328
+ "RobotsCountAnalyticsDataDto": {
1329
+ "type": "object",
1330
+ "properties": {
1331
+ "data": {
1332
+ "$ref": "#/components/schemas/RobotsCountResponseDto"
1333
+ },
1334
+ "timestamp": {
1335
+ "type": "string",
1336
+ "example": "2026-05-29T12:00:00.000Z"
1337
+ },
1338
+ "status": {
1339
+ "type": "string",
1340
+ "enum": ["success", "error"],
1341
+ "example": "success"
1342
+ }
1343
+ },
1344
+ "required": ["data", "timestamp", "status"]
1345
+ },
1346
+ "RobotsCountResponseDto": {
1347
+ "type": "object",
1348
+ "properties": {
1349
+ "total": {
1350
+ "type": "number",
1351
+ "example": 24
1352
+ },
1353
+ "online": {
1354
+ "type": "number",
1355
+ "example": 20
1356
+ },
1357
+ "offline": {
1358
+ "type": "number",
1359
+ "example": 4
1360
+ },
1361
+ "aiEnabled": {
1362
+ "type": "number",
1363
+ "example": 6
1364
+ },
1365
+ "swUpdatePending": {
1366
+ "type": "number",
1367
+ "example": 2
1368
+ }
1369
+ },
1370
+ "required": [
1371
+ "total",
1372
+ "online",
1373
+ "offline",
1374
+ "aiEnabled",
1375
+ "swUpdatePending"
1376
+ ]
1377
+ }
1378
+ }
1379
+ }
1380
+ }