@xtrape/capsule-contracts-node 0.1.0-public-review.0

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.
@@ -0,0 +1,2085 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: Opstage CE API
4
+ version: 0.1.0
5
+ summary: CE v0.1 API contract for Opstage Capsule Service governance.
6
+ license:
7
+ name: Apache-2.0
8
+ identifier: Apache-2.0
9
+ contact:
10
+ name: Opstage CE maintainers
11
+ url: https://github.com/xtrape/xtrape-capsule-ce
12
+ description: |
13
+ This contract defines the CE v0.1 Admin API, Agent API, and System API.
14
+ Admin API uses /api/admin/*, Agent API uses /api/agents/*, and System API uses /api/system/*.
15
+
16
+ ## Standard Error Responses
17
+
18
+ Any endpoint MAY return one of the following standard error responses. The
19
+ canonical list of `error.code` values lives in `09-contracts/errors.md`.
20
+
21
+ 400 Bad Request - malformed request (#/components/responses/BadRequest)
22
+ 401 Unauthorized - missing or invalid auth (#/components/responses/Unauthorized)
23
+ 403 Forbidden - allowed-but-blocked, CSRF mismatch (#/components/responses/Forbidden)
24
+ 404 Not Found - resource missing (#/components/responses/NotFound)
25
+ 409 Conflict - duplicate / terminal state (#/components/responses/Conflict)
26
+ 410 Gone - expired Command (#/components/responses/Gone)
27
+ 422 Unprocessable Entity - validation failed (#/components/responses/UnprocessableEntity)
28
+ 500 Internal Server Error- unexpected error (#/components/responses/InternalError)
29
+
30
+ Endpoint-specific entries below add or override these where useful, but every
31
+ endpoint should be assumed to be capable of returning the relevant subset of
32
+ the above. Backend MUST always wrap error bodies in `ErrorEnvelope`.
33
+
34
+ ## Authentication and CSRF
35
+
36
+ Admin API: cookie-based session (`opstage_session`, HttpOnly, Secure,
37
+ SameSite=Lax). Every state-changing call (POST/PUT/PATCH/DELETE) MUST
38
+ include the `X-CSRF-Token` header carrying the value returned by
39
+ `POST /api/admin/auth/login`, `GET /api/admin/auth/me`, or
40
+ `GET /api/admin/auth/csrf`. Mismatch returns `403 CSRF_INVALID`.
41
+
42
+ Agent API: `Authorization: Bearer <opstage_agent_token>` only. Agents
43
+ MUST NOT send cookies, and CSRF tokens do not apply.
44
+
45
+ System API: unauthenticated; safe to expose to liveness/readiness probes.
46
+
47
+ ## Pagination, Sorting, and Filtering Conventions
48
+
49
+ All list endpoints follow these conventions unless an endpoint explicitly
50
+ states otherwise:
51
+
52
+ - `page` (>= 1, default 1)
53
+ - `pageSize` (1..100, default 20)
54
+ - `sort` (e.g. `-createdAt,name` for descending then ascending). Allowed
55
+ fields are documented per endpoint; unknown fields return
56
+ `422 VALIDATION_FAILED`.
57
+ - Filters are individual query params named after the filtered field
58
+ (e.g. `status=HEALTHY`, `agentId=agt_...`). Multiple values for the
59
+ same key are OR'ed; multiple keys are AND'ed.
60
+ - Response body is `{ data: T[], pagination: { page, pageSize, total } }`
61
+ wrapped by `SuccessEnvelope`.
62
+
63
+ The Backend MUST validate every query param with the matching Zod schema
64
+ in `packages/contracts` before reaching the data layer.
65
+ servers:
66
+ - url: http://localhost:8080
67
+ description: Local CE development server
68
+ security: []
69
+ tags:
70
+ - name: AdminAuth
71
+ description: Admin login, logout, session inspection, and CSRF refresh.
72
+ - name: AdminDashboard
73
+ description: Aggregate counters for the CE governance loop.
74
+ - name: AdminAgents
75
+ description: List, inspect, and manage Agents bound to Capsule Services.
76
+ - name: AdminRegistrationTokens
77
+ description: Create, list, and revoke single-use registration tokens.
78
+ - name: AdminCapsuleServices
79
+ description: Inspect Capsule Services and request predefined actions.
80
+ - name: AdminCommands
81
+ description: Inspect Commands created via Capsule Service actions.
82
+ - name: AdminAuditEvents
83
+ description: Read the immutable audit trail.
84
+ - name: Agent
85
+ description: Agent-side bearer-token API used by the Node Embedded Agent SDK.
86
+ - name: System
87
+ description: Unauthenticated probes for liveness and version metadata.
88
+ paths:
89
+ /api/admin/auth/login:
90
+ post:
91
+ tags: [AdminAuth]
92
+ summary: Log in an admin user.
93
+ description: |
94
+ Validates credentials and starts an admin session. On success the
95
+ Backend MUST set the `opstage_session` cookie (HttpOnly, Secure,
96
+ SameSite=Lax, Path=/, Max-Age per ADR 0004) and return a fresh
97
+ `csrfToken` in the response body. All subsequent state-changing
98
+ Admin API calls (POST/PUT/PATCH/DELETE) MUST include the same value
99
+ in the `X-CSRF-Token` header (double-submit token pattern).
100
+ operationId: adminLogin
101
+ requestBody:
102
+ required: true
103
+ content:
104
+ application/json:
105
+ schema:
106
+ $ref: '#/components/schemas/AdminLoginRequest'
107
+ example:
108
+ username: admin
109
+ password: change-on-first-login
110
+ responses:
111
+ '200':
112
+ description: Login succeeded. Session cookie is set and CSRF token returned.
113
+ headers:
114
+ Set-Cookie:
115
+ description: |
116
+ Sets the admin session cookie. Format:
117
+ `opstage_session=<opaque>; Path=/; HttpOnly; Secure;
118
+ SameSite=Lax; Max-Age=<sessionTtlSeconds>`.
119
+ schema:
120
+ type: string
121
+ content:
122
+ application/json:
123
+ schema:
124
+ allOf:
125
+ - $ref: '#/components/schemas/SuccessEnvelope'
126
+ - type: object
127
+ properties:
128
+ data:
129
+ $ref: '#/components/schemas/AdminSession'
130
+ example:
131
+ success: true
132
+ data:
133
+ user:
134
+ id: usr_01HXYZABC1234567
135
+ username: admin
136
+ displayName: Admin
137
+ role: owner
138
+ status: ACTIVE
139
+ csrfToken: 7f3c1e8e9b2c4a8c91a0a7c2e1d3b5f6
140
+ expiresAt: '2026-05-01T13:30:00.000Z'
141
+ '401':
142
+ $ref: '#/components/responses/Unauthorized'
143
+ '422':
144
+ $ref: '#/components/responses/UnprocessableEntity'
145
+ /api/admin/auth/logout:
146
+ post:
147
+ tags: [AdminAuth]
148
+ summary: Log out current admin session.
149
+ description: |
150
+ Invalidates the current admin session and clears the
151
+ `opstage_session` cookie. Requires a valid CSRF header.
152
+ operationId: adminLogout
153
+ security:
154
+ - AdminSessionCookie: []
155
+ parameters:
156
+ - $ref: '#/components/parameters/CsrfToken'
157
+ responses:
158
+ '200':
159
+ description: Logout succeeded. Session cookie is cleared.
160
+ headers:
161
+ Set-Cookie:
162
+ description: |
163
+ Clears the admin session cookie. Format:
164
+ `opstage_session=; Path=/; HttpOnly; Secure;
165
+ SameSite=Lax; Max-Age=0`.
166
+ schema:
167
+ type: string
168
+ content:
169
+ application/json:
170
+ schema:
171
+ $ref: '#/components/schemas/SuccessEnvelope'
172
+ '401':
173
+ $ref: '#/components/responses/Unauthorized'
174
+ '403':
175
+ $ref: '#/components/responses/Forbidden'
176
+ /api/admin/auth/me:
177
+ get:
178
+ tags: [AdminAuth]
179
+ summary: Get current admin user.
180
+ description: |
181
+ Returns the current admin session. Useful for UI bootstrap to detect
182
+ whether the user is logged in and to refresh the CSRF token.
183
+ operationId: adminMe
184
+ security:
185
+ - AdminSessionCookie: []
186
+ responses:
187
+ '200':
188
+ description: Current admin user.
189
+ content:
190
+ application/json:
191
+ schema:
192
+ allOf:
193
+ - $ref: '#/components/schemas/SuccessEnvelope'
194
+ - type: object
195
+ properties:
196
+ data:
197
+ $ref: '#/components/schemas/AdminSession'
198
+ example:
199
+ success: true
200
+ data:
201
+ user:
202
+ id: usr_01HXYZABC1234567
203
+ username: admin
204
+ displayName: Admin
205
+ role: owner
206
+ status: ACTIVE
207
+ csrfToken: 7f3c1e8e9b2c4a8c91a0a7c2e1d3b5f6
208
+ expiresAt: '2026-05-01T13:30:00.000Z'
209
+ '401':
210
+ $ref: '#/components/responses/Unauthorized'
211
+ /api/admin/auth/csrf:
212
+ get:
213
+ tags: [AdminAuth]
214
+ summary: Refresh the CSRF token for the current session.
215
+ description: |
216
+ Returns a new CSRF token bound to the current `opstage_session`
217
+ cookie. The Backend MUST rotate the stored token on every call so
218
+ old tokens stop validating. Used by the UI right before
219
+ long-form interactions, or after a 403 `CSRF_INVALID` response.
220
+ operationId: adminCsrf
221
+ security:
222
+ - AdminSessionCookie: []
223
+ responses:
224
+ '200':
225
+ description: New CSRF token for the active session.
226
+ content:
227
+ application/json:
228
+ schema:
229
+ allOf:
230
+ - $ref: '#/components/schemas/SuccessEnvelope'
231
+ - type: object
232
+ properties:
233
+ data:
234
+ type: object
235
+ required: [csrfToken]
236
+ properties:
237
+ csrfToken:
238
+ type: string
239
+ example:
240
+ success: true
241
+ data:
242
+ csrfToken: 7f3c1e8e9b2c4a8c91a0a7c2e1d3b5f6
243
+ '401':
244
+ $ref: '#/components/responses/Unauthorized'
245
+ /api/admin/dashboard/summary:
246
+ get:
247
+ tags: [AdminDashboard]
248
+ summary: Get CE dashboard summary.
249
+ operationId: getDashboardSummary
250
+ security:
251
+ - AdminSessionCookie: []
252
+ responses:
253
+ '200':
254
+ description: Dashboard summary.
255
+ content:
256
+ application/json:
257
+ schema:
258
+ allOf:
259
+ - $ref: '#/components/schemas/SuccessEnvelope'
260
+ - type: object
261
+ properties:
262
+ data:
263
+ $ref: '#/components/schemas/DashboardSummary'
264
+ /api/admin/agents:
265
+ get:
266
+ tags: [AdminAgents]
267
+ summary: List Agents.
268
+ description: |
269
+ Allowed `sort` fields: `createdAt`, `lastSeenAt`, `name`. Default
270
+ sort: `-createdAt`. Filter `status` accepts multiple values
271
+ (OR'ed).
272
+ operationId: listAgents
273
+ security:
274
+ - AdminSessionCookie: []
275
+ parameters:
276
+ - $ref: '#/components/parameters/Page'
277
+ - $ref: '#/components/parameters/PageSize'
278
+ - $ref: '#/components/parameters/Sort'
279
+ - name: status
280
+ in: query
281
+ explode: true
282
+ schema:
283
+ type: array
284
+ items:
285
+ $ref: '#/components/schemas/AgentStatus'
286
+ responses:
287
+ '200':
288
+ description: Agent list.
289
+ content:
290
+ application/json:
291
+ schema:
292
+ allOf:
293
+ - $ref: '#/components/schemas/SuccessEnvelope'
294
+ - type: object
295
+ properties:
296
+ data:
297
+ type: array
298
+ items:
299
+ $ref: '#/components/schemas/Agent'
300
+ pagination:
301
+ $ref: '#/components/schemas/Pagination'
302
+ example:
303
+ success: true
304
+ data:
305
+ - id: agt_01HXYZAGT1234567
306
+ code: demo-prod-1
307
+ name: Demo Prod Agent 1
308
+ mode: embedded
309
+ runtime: nodejs
310
+ status: ONLINE
311
+ lastHeartbeatAt: '2026-05-01T12:00:00.000Z'
312
+ createdAt: '2026-04-30T10:00:00.000Z'
313
+ updatedAt: '2026-05-01T12:00:00.000Z'
314
+ pagination:
315
+ page: 1
316
+ pageSize: 20
317
+ total: 1
318
+ '401':
319
+ $ref: '#/components/responses/Unauthorized'
320
+ '422':
321
+ $ref: '#/components/responses/UnprocessableEntity'
322
+ /api/admin/agents/{agentId}:
323
+ get:
324
+ tags: [AdminAgents]
325
+ summary: Get Agent detail.
326
+ operationId: getAgent
327
+ security:
328
+ - AdminSessionCookie: []
329
+ parameters:
330
+ - $ref: '#/components/parameters/AgentId'
331
+ responses:
332
+ '200':
333
+ description: Agent detail.
334
+ content:
335
+ application/json:
336
+ schema:
337
+ allOf:
338
+ - $ref: '#/components/schemas/SuccessEnvelope'
339
+ - type: object
340
+ properties:
341
+ data:
342
+ $ref: '#/components/schemas/Agent'
343
+ '404':
344
+ $ref: '#/components/responses/NotFound'
345
+ /api/admin/agents/{agentId}/disable:
346
+ post:
347
+ tags: [AdminAgents]
348
+ summary: Disable an Agent.
349
+ operationId: disableAgent
350
+ security:
351
+ - AdminSessionCookie: []
352
+ parameters:
353
+ - $ref: '#/components/parameters/AgentId'
354
+ - $ref: '#/components/parameters/CsrfToken'
355
+ responses:
356
+ '200':
357
+ $ref: '#/components/responses/Ok'
358
+ '401':
359
+ $ref: '#/components/responses/Unauthorized'
360
+ '403':
361
+ $ref: '#/components/responses/Forbidden'
362
+ '404':
363
+ $ref: '#/components/responses/NotFound'
364
+ /api/admin/agents/{agentId}/enable:
365
+ post:
366
+ tags: [AdminAgents]
367
+ summary: Enable an Agent.
368
+ operationId: enableAgent
369
+ security:
370
+ - AdminSessionCookie: []
371
+ parameters:
372
+ - $ref: '#/components/parameters/AgentId'
373
+ - $ref: '#/components/parameters/CsrfToken'
374
+ responses:
375
+ '200':
376
+ $ref: '#/components/responses/Ok'
377
+ '401':
378
+ $ref: '#/components/responses/Unauthorized'
379
+ '403':
380
+ $ref: '#/components/responses/Forbidden'
381
+ '404':
382
+ $ref: '#/components/responses/NotFound'
383
+ /api/admin/agents/{agentId}/revoke:
384
+ post:
385
+ tags: [AdminAgents]
386
+ summary: Revoke an Agent.
387
+ operationId: revokeAgent
388
+ security:
389
+ - AdminSessionCookie: []
390
+ parameters:
391
+ - $ref: '#/components/parameters/AgentId'
392
+ - $ref: '#/components/parameters/CsrfToken'
393
+ responses:
394
+ '200':
395
+ $ref: '#/components/responses/Ok'
396
+ '401':
397
+ $ref: '#/components/responses/Unauthorized'
398
+ '403':
399
+ $ref: '#/components/responses/Forbidden'
400
+ '404':
401
+ $ref: '#/components/responses/NotFound'
402
+ /api/admin/registration-tokens:
403
+ post:
404
+ tags: [AdminRegistrationTokens]
405
+ summary: Create a registration token.
406
+ description: |
407
+ Creates a single-use registration token. The raw token is returned
408
+ ONCE in `data.rawToken`; only its SHA-256 hash is persisted. Subsequent
409
+ list calls do not include `rawToken`.
410
+ operationId: createRegistrationToken
411
+ security:
412
+ - AdminSessionCookie: []
413
+ parameters:
414
+ - $ref: '#/components/parameters/CsrfToken'
415
+ requestBody:
416
+ required: false
417
+ content:
418
+ application/json:
419
+ schema:
420
+ $ref: '#/components/schemas/CreateRegistrationTokenRequest'
421
+ example:
422
+ name: demo-host-1
423
+ expiresInSeconds: 3600
424
+ responses:
425
+ '200':
426
+ description: Registration token created. Raw token is shown only once.
427
+ content:
428
+ application/json:
429
+ schema:
430
+ allOf:
431
+ - $ref: '#/components/schemas/SuccessEnvelope'
432
+ - type: object
433
+ properties:
434
+ data:
435
+ $ref: '#/components/schemas/CreateRegistrationTokenResponse'
436
+ example:
437
+ success: true
438
+ data:
439
+ id: tok_01HXYZTOK1234567
440
+ name: demo-host-1
441
+ status: ACTIVE
442
+ expiresAt: '2026-05-01T13:00:00.000Z'
443
+ createdAt: '2026-05-01T12:00:00.000Z'
444
+ rawToken: opstage_reg_4xKj8mNpQ2rT5v7yA9bC1dF3gH6iJ0kL
445
+ '401':
446
+ $ref: '#/components/responses/Unauthorized'
447
+ '403':
448
+ $ref: '#/components/responses/Forbidden'
449
+ '422':
450
+ $ref: '#/components/responses/UnprocessableEntity'
451
+ get:
452
+ tags: [AdminRegistrationTokens]
453
+ summary: List registration tokens without raw token values.
454
+ description: |
455
+ Allowed `sort` fields: `createdAt`, `expiresAt`. Default: `-createdAt`.
456
+ Filter `status` accepts multiple values (OR'ed).
457
+ operationId: listRegistrationTokens
458
+ security:
459
+ - AdminSessionCookie: []
460
+ parameters:
461
+ - $ref: '#/components/parameters/Page'
462
+ - $ref: '#/components/parameters/PageSize'
463
+ - $ref: '#/components/parameters/Sort'
464
+ - name: status
465
+ in: query
466
+ explode: true
467
+ schema:
468
+ type: array
469
+ items:
470
+ $ref: '#/components/schemas/TokenStatus'
471
+ responses:
472
+ '200':
473
+ description: Registration token list.
474
+ content:
475
+ application/json:
476
+ schema:
477
+ allOf:
478
+ - $ref: '#/components/schemas/SuccessEnvelope'
479
+ - type: object
480
+ properties:
481
+ data:
482
+ type: array
483
+ items:
484
+ $ref: '#/components/schemas/RegistrationToken'
485
+ pagination:
486
+ $ref: '#/components/schemas/Pagination'
487
+ '401':
488
+ $ref: '#/components/responses/Unauthorized'
489
+ '422':
490
+ $ref: '#/components/responses/UnprocessableEntity'
491
+ /api/admin/registration-tokens/{tokenId}/revoke:
492
+ post:
493
+ tags: [AdminRegistrationTokens]
494
+ summary: Revoke a registration token.
495
+ operationId: revokeRegistrationToken
496
+ security:
497
+ - AdminSessionCookie: []
498
+ parameters:
499
+ - $ref: '#/components/parameters/TokenId'
500
+ - $ref: '#/components/parameters/CsrfToken'
501
+ responses:
502
+ '200':
503
+ $ref: '#/components/responses/Ok'
504
+ '401':
505
+ $ref: '#/components/responses/Unauthorized'
506
+ '403':
507
+ $ref: '#/components/responses/Forbidden'
508
+ '404':
509
+ $ref: '#/components/responses/NotFound'
510
+ /api/admin/capsule-services:
511
+ get:
512
+ tags: [AdminCapsuleServices]
513
+ summary: List Capsule Services.
514
+ description: |
515
+ Allowed `sort` fields: `createdAt`, `lastReportedAt`, `code`, `name`.
516
+ Default sort: `-createdAt`. Filter `status` accepts multiple values
517
+ (OR'ed); `agentId` filters services owned by a specific Agent.
518
+ operationId: listCapsuleServices
519
+ security:
520
+ - AdminSessionCookie: []
521
+ parameters:
522
+ - $ref: '#/components/parameters/Page'
523
+ - $ref: '#/components/parameters/PageSize'
524
+ - $ref: '#/components/parameters/Sort'
525
+ - name: status
526
+ in: query
527
+ explode: true
528
+ schema:
529
+ type: array
530
+ items:
531
+ $ref: '#/components/schemas/CapsuleServiceStatus'
532
+ - name: agentId
533
+ in: query
534
+ schema:
535
+ type: string
536
+ pattern: '^agt_'
537
+ responses:
538
+ '200':
539
+ description: Capsule Service list.
540
+ content:
541
+ application/json:
542
+ schema:
543
+ allOf:
544
+ - $ref: '#/components/schemas/SuccessEnvelope'
545
+ - type: object
546
+ properties:
547
+ data:
548
+ type: array
549
+ items:
550
+ $ref: '#/components/schemas/CapsuleService'
551
+ pagination:
552
+ $ref: '#/components/schemas/Pagination'
553
+ example:
554
+ success: true
555
+ data:
556
+ - id: svc_01HXYZSVC1234567
557
+ agentId: agt_01HXYZAGT1234567
558
+ code: demo-billing
559
+ name: Demo Billing Service
560
+ runtime: nodejs
561
+ status: HEALTHY
562
+ healthStatus: UP
563
+ lastReportedAt: '2026-05-01T12:00:00.000Z'
564
+ lastHealthAt: '2026-05-01T12:00:00.000Z'
565
+ createdAt: '2026-04-30T10:00:00.000Z'
566
+ updatedAt: '2026-05-01T12:00:00.000Z'
567
+ pagination:
568
+ page: 1
569
+ pageSize: 20
570
+ total: 1
571
+ '401':
572
+ $ref: '#/components/responses/Unauthorized'
573
+ '422':
574
+ $ref: '#/components/responses/UnprocessableEntity'
575
+ /api/admin/capsule-services/{serviceId}:
576
+ get:
577
+ tags: [AdminCapsuleServices]
578
+ summary: Get Capsule Service detail.
579
+ operationId: getCapsuleService
580
+ security:
581
+ - AdminSessionCookie: []
582
+ parameters:
583
+ - $ref: '#/components/parameters/ServiceId'
584
+ responses:
585
+ '200':
586
+ description: Capsule Service detail.
587
+ content:
588
+ application/json:
589
+ schema:
590
+ allOf:
591
+ - $ref: '#/components/schemas/SuccessEnvelope'
592
+ - type: object
593
+ properties:
594
+ data:
595
+ $ref: '#/components/schemas/CapsuleServiceDetail'
596
+ '404':
597
+ $ref: '#/components/responses/NotFound'
598
+ /api/admin/capsule-services/{serviceId}/manifest:
599
+ get:
600
+ tags: [AdminCapsuleServices]
601
+ summary: Get Capsule Service manifest JSON.
602
+ operationId: getCapsuleServiceManifest
603
+ security:
604
+ - AdminSessionCookie: []
605
+ parameters:
606
+ - $ref: '#/components/parameters/ServiceId'
607
+ responses:
608
+ '200':
609
+ description: Manifest JSON.
610
+ content:
611
+ application/json:
612
+ schema:
613
+ allOf:
614
+ - $ref: '#/components/schemas/SuccessEnvelope'
615
+ - type: object
616
+ properties:
617
+ data:
618
+ type: object
619
+ additionalProperties: true
620
+ /api/admin/capsule-services/{serviceId}/health:
621
+ get:
622
+ tags: [AdminCapsuleServices]
623
+ summary: Get latest health report.
624
+ operationId: getCapsuleServiceHealth
625
+ security:
626
+ - AdminSessionCookie: []
627
+ parameters:
628
+ - $ref: '#/components/parameters/ServiceId'
629
+ responses:
630
+ '200':
631
+ description: Latest health report.
632
+ content:
633
+ application/json:
634
+ schema:
635
+ allOf:
636
+ - $ref: '#/components/schemas/SuccessEnvelope'
637
+ - type: object
638
+ properties:
639
+ data:
640
+ $ref: '#/components/schemas/HealthReport'
641
+ /api/admin/capsule-services/{serviceId}/configs:
642
+ get:
643
+ tags: [AdminCapsuleServices]
644
+ summary: List config metadata.
645
+ operationId: listCapsuleServiceConfigs
646
+ security:
647
+ - AdminSessionCookie: []
648
+ parameters:
649
+ - $ref: '#/components/parameters/ServiceId'
650
+ responses:
651
+ '200':
652
+ description: Config metadata list.
653
+ content:
654
+ application/json:
655
+ schema:
656
+ allOf:
657
+ - $ref: '#/components/schemas/SuccessEnvelope'
658
+ - type: object
659
+ properties:
660
+ data:
661
+ type: array
662
+ items:
663
+ $ref: '#/components/schemas/ConfigItem'
664
+ /api/admin/capsule-services/{serviceId}/actions:
665
+ get:
666
+ tags: [AdminCapsuleServices]
667
+ summary: List action definitions.
668
+ operationId: listCapsuleServiceActions
669
+ security:
670
+ - AdminSessionCookie: []
671
+ parameters:
672
+ - $ref: '#/components/parameters/ServiceId'
673
+ responses:
674
+ '200':
675
+ description: Action definition list.
676
+ content:
677
+ application/json:
678
+ schema:
679
+ allOf:
680
+ - $ref: '#/components/schemas/SuccessEnvelope'
681
+ - type: object
682
+ properties:
683
+ data:
684
+ type: array
685
+ items:
686
+ $ref: '#/components/schemas/ActionDefinition'
687
+ /api/admin/capsule-services/{serviceId}/actions/{actionName}:
688
+ post:
689
+ tags: [AdminCapsuleServices]
690
+ summary: Request predefined action execution.
691
+ description: |
692
+ Validates the action and creates a `Command` row in `PENDING` state.
693
+ Returns the freshly created Command. UI MAY then poll
694
+ `GET /api/admin/commands/{commandId}` for terminal status.
695
+ See `04-opstage/04-command-and-action-model.md` §23 for the
696
+ canonical sequence and validation rules.
697
+ operationId: createActionCommand
698
+ security:
699
+ - AdminSessionCookie: []
700
+ parameters:
701
+ - $ref: '#/components/parameters/ServiceId'
702
+ - $ref: '#/components/parameters/ActionName'
703
+ - $ref: '#/components/parameters/CsrfToken'
704
+ requestBody:
705
+ required: false
706
+ content:
707
+ application/json:
708
+ schema:
709
+ $ref: '#/components/schemas/CreateActionCommandRequest'
710
+ example:
711
+ payload:
712
+ graceful: true
713
+ confirmation: true
714
+ responses:
715
+ '200':
716
+ description: Command created.
717
+ content:
718
+ application/json:
719
+ schema:
720
+ allOf:
721
+ - $ref: '#/components/schemas/SuccessEnvelope'
722
+ - type: object
723
+ properties:
724
+ data:
725
+ $ref: '#/components/schemas/Command'
726
+ example:
727
+ success: true
728
+ data:
729
+ id: cmd_01HXYZCMD1234567
730
+ agentId: agt_01HXYZAGT1234567
731
+ serviceId: svc_01HXYZSVC1234567
732
+ type: ACTION_EXECUTE
733
+ actionName: restart
734
+ payload:
735
+ graceful: true
736
+ status: PENDING
737
+ createdAt: '2026-05-01T12:00:00.000Z'
738
+ expiresAt: '2026-05-01T12:05:00.000Z'
739
+ createdByUserId: usr_01HXYZABC1234567
740
+ '401':
741
+ $ref: '#/components/responses/Unauthorized'
742
+ '403':
743
+ $ref: '#/components/responses/Forbidden'
744
+ '404':
745
+ $ref: '#/components/responses/NotFound'
746
+ '422':
747
+ $ref: '#/components/responses/UnprocessableEntity'
748
+ default:
749
+ $ref: '#/components/responses/InternalError'
750
+ /api/admin/commands:
751
+ get:
752
+ tags: [AdminCommands]
753
+ summary: List Commands.
754
+ description: |
755
+ Allowed `sort` fields: `createdAt`, `finishedAt`. Default: `-createdAt`.
756
+ Filter `status` accepts multiple values (OR'ed). `serviceId` and
757
+ `agentId` filter by Capsule Service / Agent ownership.
758
+ operationId: listCommands
759
+ security:
760
+ - AdminSessionCookie: []
761
+ parameters:
762
+ - $ref: '#/components/parameters/Page'
763
+ - $ref: '#/components/parameters/PageSize'
764
+ - $ref: '#/components/parameters/Sort'
765
+ - name: status
766
+ in: query
767
+ explode: true
768
+ schema:
769
+ type: array
770
+ items:
771
+ $ref: '#/components/schemas/CommandStatus'
772
+ - name: serviceId
773
+ in: query
774
+ schema:
775
+ type: string
776
+ pattern: '^svc_'
777
+ - name: agentId
778
+ in: query
779
+ schema:
780
+ type: string
781
+ pattern: '^agt_'
782
+ responses:
783
+ '200':
784
+ description: Command list.
785
+ content:
786
+ application/json:
787
+ schema:
788
+ allOf:
789
+ - $ref: '#/components/schemas/SuccessEnvelope'
790
+ - type: object
791
+ properties:
792
+ data:
793
+ type: array
794
+ items:
795
+ $ref: '#/components/schemas/Command'
796
+ pagination:
797
+ $ref: '#/components/schemas/Pagination'
798
+ example:
799
+ success: true
800
+ data:
801
+ - id: cmd_01HXYZCMD1234567
802
+ agentId: agt_01HXYZAGT1234567
803
+ serviceId: svc_01HXYZSVC1234567
804
+ type: ACTION_EXECUTE
805
+ actionName: restart
806
+ status: SUCCEEDED
807
+ createdAt: '2026-05-01T12:00:00.000Z'
808
+ startedAt: '2026-05-01T12:00:00.500Z'
809
+ completedAt: '2026-05-01T12:00:03.500Z'
810
+ pagination:
811
+ page: 1
812
+ pageSize: 20
813
+ total: 1
814
+ '401':
815
+ $ref: '#/components/responses/Unauthorized'
816
+ '422':
817
+ $ref: '#/components/responses/UnprocessableEntity'
818
+ /api/admin/commands/{commandId}:
819
+ get:
820
+ tags: [AdminCommands]
821
+ summary: Get Command detail.
822
+ operationId: getCommand
823
+ security:
824
+ - AdminSessionCookie: []
825
+ parameters:
826
+ - $ref: '#/components/parameters/CommandId'
827
+ responses:
828
+ '200':
829
+ description: Command detail.
830
+ content:
831
+ application/json:
832
+ schema:
833
+ allOf:
834
+ - $ref: '#/components/schemas/SuccessEnvelope'
835
+ - type: object
836
+ properties:
837
+ data:
838
+ $ref: '#/components/schemas/CommandDetail'
839
+ /api/admin/audit-events:
840
+ get:
841
+ tags: [AdminAuditEvents]
842
+ summary: List AuditEvents.
843
+ description: |
844
+ Allowed `sort` fields: `createdAt`. Default: `-createdAt`.
845
+ Filters: `actorType`, `targetType`, `targetId`, `result`, `from`/`to`
846
+ (ISO-8601, applied to `createdAt`).
847
+ operationId: listAuditEvents
848
+ security:
849
+ - AdminSessionCookie: []
850
+ parameters:
851
+ - $ref: '#/components/parameters/Page'
852
+ - $ref: '#/components/parameters/PageSize'
853
+ - $ref: '#/components/parameters/Sort'
854
+ - name: actorType
855
+ in: query
856
+ schema:
857
+ $ref: '#/components/schemas/AuditActorType'
858
+ - name: targetType
859
+ in: query
860
+ schema:
861
+ type: string
862
+ - name: targetId
863
+ in: query
864
+ schema:
865
+ type: string
866
+ - name: result
867
+ in: query
868
+ schema:
869
+ $ref: '#/components/schemas/AuditResult'
870
+ - name: from
871
+ in: query
872
+ description: Inclusive lower bound applied to `createdAt`.
873
+ schema:
874
+ type: string
875
+ format: date-time
876
+ - name: to
877
+ in: query
878
+ description: Inclusive upper bound applied to `createdAt`.
879
+ schema:
880
+ type: string
881
+ format: date-time
882
+ responses:
883
+ '200':
884
+ description: AuditEvent list.
885
+ content:
886
+ application/json:
887
+ schema:
888
+ allOf:
889
+ - $ref: '#/components/schemas/SuccessEnvelope'
890
+ - type: object
891
+ properties:
892
+ data:
893
+ type: array
894
+ items:
895
+ $ref: '#/components/schemas/AuditEvent'
896
+ pagination:
897
+ $ref: '#/components/schemas/Pagination'
898
+ example:
899
+ success: true
900
+ data:
901
+ - id: aud_01HXYZAUD1234567
902
+ createdAt: '2026-05-01T12:00:00.000Z'
903
+ actorType: USER
904
+ actorId: usr_01HXYZABC1234567
905
+ action: agent.disabled
906
+ targetType: Agent
907
+ targetId: agt_01HXYZAGT1234567
908
+ result: SUCCESS
909
+ message: Disabled by admin.
910
+ pagination:
911
+ page: 1
912
+ pageSize: 20
913
+ total: 1
914
+ '401':
915
+ $ref: '#/components/responses/Unauthorized'
916
+ '422':
917
+ $ref: '#/components/responses/UnprocessableEntity'
918
+ /api/admin/audit-events/{auditEventId}:
919
+ get:
920
+ tags: [AdminAuditEvents]
921
+ summary: Get AuditEvent detail.
922
+ operationId: getAuditEvent
923
+ security:
924
+ - AdminSessionCookie: []
925
+ parameters:
926
+ - $ref: '#/components/parameters/AuditEventId'
927
+ responses:
928
+ '200':
929
+ description: AuditEvent detail.
930
+ content:
931
+ application/json:
932
+ schema:
933
+ allOf:
934
+ - $ref: '#/components/schemas/SuccessEnvelope'
935
+ - type: object
936
+ properties:
937
+ data:
938
+ $ref: '#/components/schemas/AuditEvent'
939
+ /api/agents/register:
940
+ post:
941
+ tags: [Agent]
942
+ summary: Register an Agent with a registration token.
943
+ description: |
944
+ Consumes a single-use registration token and creates an Agent row.
945
+ The Backend returns a fresh Agent bearer token in `data.agentToken`
946
+ ONCE — only its hash is persisted. Agent SDK MUST persist the token
947
+ in a write-once secure file or in-memory and supply it in
948
+ `Authorization: Bearer <token>` for all subsequent calls.
949
+ operationId: registerAgent
950
+ requestBody:
951
+ required: true
952
+ content:
953
+ application/json:
954
+ schema:
955
+ $ref: '#/components/schemas/RegisterAgentRequest'
956
+ example:
957
+ registrationToken: opstage_reg_4xKj8mNpQ2rT5v7yA9bC1dF3gH6iJ0kL
958
+ agent:
959
+ code: demo-prod-1
960
+ name: Demo Prod Agent 1
961
+ mode: embedded
962
+ runtime: nodejs
963
+ responses:
964
+ '200':
965
+ description: Agent registered. Agent token is returned once.
966
+ content:
967
+ application/json:
968
+ schema:
969
+ allOf:
970
+ - $ref: '#/components/schemas/SuccessEnvelope'
971
+ - type: object
972
+ properties:
973
+ data:
974
+ $ref: '#/components/schemas/RegisterAgentResponse'
975
+ example:
976
+ success: true
977
+ data:
978
+ agentId: agt_01HXYZAGT1234567
979
+ agentToken: opstage_agent_3v7yA9bC1dF3gH6iJ0kL4xKj8mNpQ2rT
980
+ heartbeatIntervalSeconds: 30
981
+ commandPollIntervalSeconds: 5
982
+ '401':
983
+ $ref: '#/components/responses/Unauthorized'
984
+ '409':
985
+ $ref: '#/components/responses/Conflict'
986
+ '422':
987
+ $ref: '#/components/responses/UnprocessableEntity'
988
+ default:
989
+ $ref: '#/components/responses/InternalError'
990
+ /api/agents/{agentId}/heartbeat:
991
+ post:
992
+ tags: [Agent]
993
+ summary: Send Agent heartbeat and optional health report.
994
+ description: |
995
+ Updates `Agent.lastSeenAt`. Optional body fields allow the Agent to
996
+ report its overall health and any transient diagnostic info. The
997
+ response echoes the current authoritative timing defaults so an Agent
998
+ can converge on Backend-side changes without a redeploy.
999
+ operationId: agentHeartbeat
1000
+ security:
1001
+ - AgentBearerToken: []
1002
+ parameters:
1003
+ - $ref: '#/components/parameters/AgentId'
1004
+ requestBody:
1005
+ required: false
1006
+ content:
1007
+ application/json:
1008
+ schema:
1009
+ $ref: '#/components/schemas/AgentHeartbeatRequest'
1010
+ example:
1011
+ health:
1012
+ status: UP
1013
+ message: ok
1014
+ responses:
1015
+ '200':
1016
+ description: Heartbeat accepted.
1017
+ content:
1018
+ application/json:
1019
+ schema:
1020
+ allOf:
1021
+ - $ref: '#/components/schemas/SuccessEnvelope'
1022
+ - type: object
1023
+ properties:
1024
+ data:
1025
+ $ref: '#/components/schemas/AgentHeartbeatResponse'
1026
+ example:
1027
+ success: true
1028
+ data:
1029
+ heartbeatIntervalSeconds: 30
1030
+ commandPollIntervalSeconds: 5
1031
+ '401':
1032
+ $ref: '#/components/responses/Unauthorized'
1033
+ '403':
1034
+ $ref: '#/components/responses/Forbidden'
1035
+ '404':
1036
+ $ref: '#/components/responses/NotFound'
1037
+ '422':
1038
+ $ref: '#/components/responses/UnprocessableEntity'
1039
+ default:
1040
+ $ref: '#/components/responses/InternalError'
1041
+ /api/agents/{agentId}/services/report:
1042
+ post:
1043
+ tags: [Agent]
1044
+ summary: Report Capsule Service manifest, configs, actions, and health.
1045
+ description: |
1046
+ Idempotent upsert keyed by `(workspaceId, code)`. The Agent SHOULD
1047
+ report once at startup and again when its manifest changes, NOT on
1048
+ every heartbeat. Backend persists `manifest`, `configs`, `actions`,
1049
+ and the latest `health` snapshot.
1050
+ operationId: reportAgentServices
1051
+ security:
1052
+ - AgentBearerToken: []
1053
+ parameters:
1054
+ - $ref: '#/components/parameters/AgentId'
1055
+ requestBody:
1056
+ required: true
1057
+ content:
1058
+ application/json:
1059
+ schema:
1060
+ $ref: '#/components/schemas/ServiceReportRequest'
1061
+ example:
1062
+ services:
1063
+ - code: demo-billing
1064
+ name: Demo Billing Service
1065
+ runtime: nodejs
1066
+ manifest:
1067
+ kind: CapsuleService
1068
+ schemaVersion: '1.0'
1069
+ code: demo-billing
1070
+ name: Demo Billing Service
1071
+ version: 0.1.0
1072
+ runtime: nodejs
1073
+ agentMode: embedded
1074
+ configs:
1075
+ - key: BILLING_API_URL
1076
+ type: string
1077
+ valuePreview: https://billing.example.com
1078
+ sensitive: false
1079
+ actions:
1080
+ - name: restart
1081
+ label: Restart service
1082
+ dangerLevel: MEDIUM
1083
+ requiresConfirmation: true
1084
+ health:
1085
+ status: UP
1086
+ message: 5 dependencies up
1087
+ responses:
1088
+ '200':
1089
+ $ref: '#/components/responses/Ok'
1090
+ '401':
1091
+ $ref: '#/components/responses/Unauthorized'
1092
+ '403':
1093
+ $ref: '#/components/responses/Forbidden'
1094
+ '422':
1095
+ $ref: '#/components/responses/UnprocessableEntity'
1096
+ default:
1097
+ $ref: '#/components/responses/InternalError'
1098
+ /api/agents/{agentId}/commands:
1099
+ get:
1100
+ tags: [Agent]
1101
+ summary: Poll Commands assigned to the Agent.
1102
+ description: |
1103
+ Returns at most `limit` commands in `PENDING` status, ordered by
1104
+ `createdAt ASC`, atomically transitioning each to `RUNNING` before
1105
+ returning. Idempotent at the protocol level (a poll that returns no
1106
+ commands is identical to a missed poll).
1107
+ operationId: pollAgentCommands
1108
+ security:
1109
+ - AgentBearerToken: []
1110
+ parameters:
1111
+ - $ref: '#/components/parameters/AgentId'
1112
+ - name: limit
1113
+ in: query
1114
+ description: Maximum number of commands to return per poll. Default 10, max 50.
1115
+ schema:
1116
+ type: integer
1117
+ minimum: 1
1118
+ maximum: 50
1119
+ default: 10
1120
+ responses:
1121
+ '200':
1122
+ description: Commands assigned to this Agent.
1123
+ content:
1124
+ application/json:
1125
+ schema:
1126
+ allOf:
1127
+ - $ref: '#/components/schemas/SuccessEnvelope'
1128
+ - type: object
1129
+ properties:
1130
+ data:
1131
+ type: array
1132
+ items:
1133
+ $ref: '#/components/schemas/Command'
1134
+ example:
1135
+ success: true
1136
+ data:
1137
+ - id: cmd_01HXYZCMD1234567
1138
+ agentId: agt_01HXYZAGT1234567
1139
+ serviceId: svc_01HXYZSVC1234567
1140
+ type: ACTION_EXECUTE
1141
+ actionName: restart
1142
+ payload: {}
1143
+ status: RUNNING
1144
+ createdAt: '2026-05-01T12:00:00.000Z'
1145
+ startedAt: '2026-05-01T12:00:00.500Z'
1146
+ expiresAt: '2026-05-01T12:05:00.000Z'
1147
+ '401':
1148
+ $ref: '#/components/responses/Unauthorized'
1149
+ '403':
1150
+ $ref: '#/components/responses/Forbidden'
1151
+ '404':
1152
+ $ref: '#/components/responses/NotFound'
1153
+ default:
1154
+ $ref: '#/components/responses/InternalError'
1155
+ /api/agents/{agentId}/commands/{commandId}/result:
1156
+ post:
1157
+ tags: [Agent]
1158
+ summary: Report CommandResult.
1159
+ description: |
1160
+ Reports terminal CommandResult. Backend transitions the Command to
1161
+ `SUCCEEDED` (when `success: true`) or `FAILED`. A second result for
1162
+ the same Command is a `409 COMMAND_TERMINAL`. A result reported after
1163
+ the TTL has expired is a `410 COMMAND_EXPIRED` and is ignored.
1164
+ operationId: reportCommandResult
1165
+ security:
1166
+ - AgentBearerToken: []
1167
+ parameters:
1168
+ - $ref: '#/components/parameters/AgentId'
1169
+ - $ref: '#/components/parameters/CommandId'
1170
+ requestBody:
1171
+ required: true
1172
+ content:
1173
+ application/json:
1174
+ schema:
1175
+ $ref: '#/components/schemas/ReportCommandResultRequest'
1176
+ example:
1177
+ success: true
1178
+ message: Service restarted in 3.2s
1179
+ data:
1180
+ pidBefore: 1024
1181
+ pidAfter: 1097
1182
+ startedAt: '2026-05-01T12:00:00.500Z'
1183
+ finishedAt: '2026-05-01T12:00:03.700Z'
1184
+ responses:
1185
+ '200':
1186
+ $ref: '#/components/responses/Ok'
1187
+ '401':
1188
+ $ref: '#/components/responses/Unauthorized'
1189
+ '403':
1190
+ $ref: '#/components/responses/Forbidden'
1191
+ '404':
1192
+ $ref: '#/components/responses/NotFound'
1193
+ '409':
1194
+ $ref: '#/components/responses/Conflict'
1195
+ '410':
1196
+ $ref: '#/components/responses/Gone'
1197
+ '422':
1198
+ $ref: '#/components/responses/UnprocessableEntity'
1199
+ default:
1200
+ $ref: '#/components/responses/InternalError'
1201
+ /api/system/health:
1202
+ get:
1203
+ tags: [System]
1204
+ summary: Get system health.
1205
+ operationId: getSystemHealth
1206
+ responses:
1207
+ '200':
1208
+ description: System health.
1209
+ content:
1210
+ application/json:
1211
+ schema:
1212
+ allOf:
1213
+ - $ref: '#/components/schemas/SuccessEnvelope'
1214
+ - type: object
1215
+ properties:
1216
+ data:
1217
+ $ref: '#/components/schemas/SystemHealth'
1218
+ /api/system/version:
1219
+ get:
1220
+ tags: [System]
1221
+ summary: Get system version.
1222
+ operationId: getSystemVersion
1223
+ responses:
1224
+ '200':
1225
+ description: System version.
1226
+ content:
1227
+ application/json:
1228
+ schema:
1229
+ allOf:
1230
+ - $ref: '#/components/schemas/SuccessEnvelope'
1231
+ - type: object
1232
+ properties:
1233
+ data:
1234
+ $ref: '#/components/schemas/SystemVersion'
1235
+ components:
1236
+ securitySchemes:
1237
+ AdminSessionCookie:
1238
+ type: apiKey
1239
+ in: cookie
1240
+ name: opstage_session
1241
+ AgentBearerToken:
1242
+ type: http
1243
+ scheme: bearer
1244
+ bearerFormat: opstage_agent token
1245
+ parameters:
1246
+ Page:
1247
+ name: page
1248
+ in: query
1249
+ schema:
1250
+ type: integer
1251
+ minimum: 1
1252
+ default: 1
1253
+ PageSize:
1254
+ name: pageSize
1255
+ in: query
1256
+ schema:
1257
+ type: integer
1258
+ minimum: 1
1259
+ maximum: 100
1260
+ default: 20
1261
+ AgentId:
1262
+ name: agentId
1263
+ in: path
1264
+ required: true
1265
+ schema:
1266
+ type: string
1267
+ pattern: '^agt_'
1268
+ ServiceId:
1269
+ name: serviceId
1270
+ in: path
1271
+ required: true
1272
+ schema:
1273
+ type: string
1274
+ pattern: '^svc_'
1275
+ CommandId:
1276
+ name: commandId
1277
+ in: path
1278
+ required: true
1279
+ schema:
1280
+ type: string
1281
+ pattern: '^cmd_'
1282
+ TokenId:
1283
+ name: tokenId
1284
+ in: path
1285
+ required: true
1286
+ schema:
1287
+ type: string
1288
+ pattern: '^tok_'
1289
+ AuditEventId:
1290
+ name: auditEventId
1291
+ in: path
1292
+ required: true
1293
+ schema:
1294
+ type: string
1295
+ pattern: '^aud_'
1296
+ ActionName:
1297
+ name: actionName
1298
+ in: path
1299
+ required: true
1300
+ schema:
1301
+ type: string
1302
+ CsrfToken:
1303
+ name: X-CSRF-Token
1304
+ in: header
1305
+ required: true
1306
+ description: |
1307
+ Double-submit CSRF token bound to the active `opstage_session`
1308
+ cookie. Required for all state-changing Admin API calls
1309
+ (POST/PUT/PATCH/DELETE). Obtain it from `POST /api/admin/auth/login`,
1310
+ `GET /api/admin/auth/me`, or `GET /api/admin/auth/csrf`.
1311
+ schema:
1312
+ type: string
1313
+ Sort:
1314
+ name: sort
1315
+ in: query
1316
+ description: |
1317
+ Sort expression. Format: `field` (ascending) or `-field` (descending).
1318
+ Multiple fields are comma-separated and applied left-to-right. The
1319
+ set of allowed fields is documented per endpoint; unknown fields
1320
+ return 422 `VALIDATION_FAILED`.
1321
+ schema:
1322
+ type: string
1323
+ responses:
1324
+ Ok:
1325
+ description: Operation succeeded.
1326
+ content:
1327
+ application/json:
1328
+ schema:
1329
+ $ref: '#/components/schemas/SuccessEnvelope'
1330
+ BadRequest:
1331
+ description: Invalid request.
1332
+ content:
1333
+ application/json:
1334
+ schema:
1335
+ $ref: '#/components/schemas/ErrorEnvelope'
1336
+ Unauthorized:
1337
+ description: Authentication failed (missing or invalid session/token).
1338
+ content:
1339
+ application/json:
1340
+ schema:
1341
+ $ref: '#/components/schemas/ErrorEnvelope'
1342
+ Forbidden:
1343
+ description: Authenticated but not allowed (includes CSRF token mismatch and disabled/revoked Agents).
1344
+ content:
1345
+ application/json:
1346
+ schema:
1347
+ $ref: '#/components/schemas/ErrorEnvelope'
1348
+ NotFound:
1349
+ description: Resource not found.
1350
+ content:
1351
+ application/json:
1352
+ schema:
1353
+ $ref: '#/components/schemas/ErrorEnvelope'
1354
+ Conflict:
1355
+ description: State conflict (duplicate resource, terminal Command, etc.).
1356
+ content:
1357
+ application/json:
1358
+ schema:
1359
+ $ref: '#/components/schemas/ErrorEnvelope'
1360
+ Gone:
1361
+ description: Resource is no longer available (expired Command).
1362
+ content:
1363
+ application/json:
1364
+ schema:
1365
+ $ref: '#/components/schemas/ErrorEnvelope'
1366
+ UnprocessableEntity:
1367
+ description: Validation failed.
1368
+ content:
1369
+ application/json:
1370
+ schema:
1371
+ $ref: '#/components/schemas/ErrorEnvelope'
1372
+ InternalError:
1373
+ description: Unexpected server error.
1374
+ content:
1375
+ application/json:
1376
+ schema:
1377
+ $ref: '#/components/schemas/ErrorEnvelope'
1378
+ schemas:
1379
+ SuccessEnvelope:
1380
+ type: object
1381
+ required: [success]
1382
+ properties:
1383
+ success:
1384
+ type: boolean
1385
+ const: true
1386
+ ErrorEnvelope:
1387
+ type: object
1388
+ required: [success, error]
1389
+ properties:
1390
+ success:
1391
+ type: boolean
1392
+ const: false
1393
+ error:
1394
+ $ref: '#/components/schemas/ErrorInfo'
1395
+ ErrorInfo:
1396
+ type: object
1397
+ required: [code, message]
1398
+ properties:
1399
+ code:
1400
+ type: string
1401
+ message:
1402
+ type: string
1403
+ details:
1404
+ type: object
1405
+ additionalProperties: true
1406
+ Pagination:
1407
+ type: object
1408
+ required: [page, pageSize, total]
1409
+ properties:
1410
+ page:
1411
+ type: integer
1412
+ pageSize:
1413
+ type: integer
1414
+ total:
1415
+ type: integer
1416
+ AdminLoginRequest:
1417
+ type: object
1418
+ required: [username, password]
1419
+ properties:
1420
+ username:
1421
+ type: string
1422
+ password:
1423
+ type: string
1424
+ format: password
1425
+ AdminSession:
1426
+ type: object
1427
+ required: [user, csrfToken, expiresAt]
1428
+ properties:
1429
+ user:
1430
+ $ref: '#/components/schemas/User'
1431
+ csrfToken:
1432
+ type: string
1433
+ description: |
1434
+ Token to be echoed in the `X-CSRF-Token` header for any
1435
+ state-changing Admin API call. Rotated on `/api/admin/auth/csrf`.
1436
+ expiresAt:
1437
+ type: string
1438
+ format: date-time
1439
+ description: |
1440
+ Absolute expiry of the current session cookie. UI may use this
1441
+ to schedule a silent refresh or warn the user before logout.
1442
+ User:
1443
+ type: object
1444
+ required: [id, username, role, status]
1445
+ properties:
1446
+ id:
1447
+ type: string
1448
+ pattern: '^usr_'
1449
+ username:
1450
+ type: string
1451
+ displayName:
1452
+ type: [string, 'null']
1453
+ role:
1454
+ type: string
1455
+ enum: [owner]
1456
+ status:
1457
+ type: string
1458
+ enum: [ACTIVE, DISABLED]
1459
+ DashboardSummary:
1460
+ type: object
1461
+ required: [agentCounts, serviceCounts, commandCounts]
1462
+ properties:
1463
+ agentCounts:
1464
+ type: object
1465
+ additionalProperties:
1466
+ type: integer
1467
+ serviceCounts:
1468
+ type: object
1469
+ additionalProperties:
1470
+ type: integer
1471
+ commandCounts:
1472
+ type: object
1473
+ additionalProperties:
1474
+ type: integer
1475
+ recentCommands:
1476
+ type: array
1477
+ items:
1478
+ $ref: '#/components/schemas/Command'
1479
+ recentAuditEvents:
1480
+ type: array
1481
+ items:
1482
+ $ref: '#/components/schemas/AuditEvent'
1483
+ AgentStatus:
1484
+ type: string
1485
+ enum: [PENDING, ONLINE, OFFLINE, DISABLED, REVOKED]
1486
+ CapsuleServiceStatus:
1487
+ type: string
1488
+ enum: [UNKNOWN, HEALTHY, UNHEALTHY, STALE, OFFLINE]
1489
+ HealthStatus:
1490
+ type: string
1491
+ enum: [UP, DEGRADED, DOWN, UNKNOWN]
1492
+ CommandStatus:
1493
+ type: string
1494
+ enum: [PENDING, RUNNING, SUCCEEDED, FAILED, EXPIRED, CANCELLED]
1495
+ DangerLevel:
1496
+ type: string
1497
+ enum: [LOW, MEDIUM, HIGH]
1498
+ AuditActorType:
1499
+ type: string
1500
+ enum: [USER, AGENT, SYSTEM]
1501
+ AuditResult:
1502
+ type: string
1503
+ enum: [SUCCESS, FAILURE]
1504
+ Agent:
1505
+ type: object
1506
+ required: [id, code, mode, status, createdAt, updatedAt]
1507
+ properties:
1508
+ id:
1509
+ type: string
1510
+ pattern: '^agt_'
1511
+ code:
1512
+ type: string
1513
+ name:
1514
+ type: [string, 'null']
1515
+ mode:
1516
+ type: string
1517
+ enum: [embedded, sidecar, external]
1518
+ runtime:
1519
+ type: [string, 'null']
1520
+ status:
1521
+ $ref: '#/components/schemas/AgentStatus'
1522
+ lastHeartbeatAt:
1523
+ type: [string, 'null']
1524
+ format: date-time
1525
+ createdAt:
1526
+ type: string
1527
+ format: date-time
1528
+ updatedAt:
1529
+ type: string
1530
+ format: date-time
1531
+ TokenStatus:
1532
+ type: string
1533
+ description: |
1534
+ Lifecycle of a registration or agent token. CE v0.1 enum.
1535
+ enum: [ACTIVE, USED, REVOKED, EXPIRED]
1536
+ RegistrationToken:
1537
+ type: object
1538
+ required: [id, name, status, createdAt]
1539
+ properties:
1540
+ id:
1541
+ type: string
1542
+ pattern: '^tok_'
1543
+ name:
1544
+ type: string
1545
+ status:
1546
+ $ref: '#/components/schemas/TokenStatus'
1547
+ expiresAt:
1548
+ type: [string, 'null']
1549
+ format: date-time
1550
+ usedAt:
1551
+ type: [string, 'null']
1552
+ format: date-time
1553
+ createdAt:
1554
+ type: string
1555
+ format: date-time
1556
+ CreateRegistrationTokenRequest:
1557
+ type: object
1558
+ properties:
1559
+ name:
1560
+ type: string
1561
+ default: Default registration token
1562
+ expiresInSeconds:
1563
+ type: integer
1564
+ minimum: 60
1565
+ CreateRegistrationTokenResponse:
1566
+ allOf:
1567
+ - $ref: '#/components/schemas/RegistrationToken'
1568
+ - type: object
1569
+ required: [rawToken]
1570
+ properties:
1571
+ rawToken:
1572
+ type: string
1573
+ description: Raw token shown only once. Persisted only as a SHA-256 hash.
1574
+ pattern: '^opstage_reg_'
1575
+ CapsuleService:
1576
+ type: object
1577
+ required: [id, code, name, status, createdAt, updatedAt]
1578
+ properties:
1579
+ id:
1580
+ type: string
1581
+ pattern: '^svc_'
1582
+ agentId:
1583
+ type: string
1584
+ pattern: '^agt_'
1585
+ code:
1586
+ type: string
1587
+ name:
1588
+ type: string
1589
+ description:
1590
+ type: [string, 'null']
1591
+ version:
1592
+ type: [string, 'null']
1593
+ runtime:
1594
+ type: [string, 'null']
1595
+ status:
1596
+ $ref: '#/components/schemas/CapsuleServiceStatus'
1597
+ healthStatus:
1598
+ $ref: '#/components/schemas/HealthStatus'
1599
+ lastReportedAt:
1600
+ type: [string, 'null']
1601
+ format: date-time
1602
+ lastHealthAt:
1603
+ type: [string, 'null']
1604
+ format: date-time
1605
+ createdAt:
1606
+ type: string
1607
+ format: date-time
1608
+ updatedAt:
1609
+ type: string
1610
+ format: date-time
1611
+ CapsuleServiceDetail:
1612
+ allOf:
1613
+ - $ref: '#/components/schemas/CapsuleService'
1614
+ - type: object
1615
+ properties:
1616
+ manifest:
1617
+ type: object
1618
+ additionalProperties: true
1619
+ health:
1620
+ $ref: '#/components/schemas/HealthReport'
1621
+ configs:
1622
+ type: array
1623
+ items:
1624
+ $ref: '#/components/schemas/ConfigItem'
1625
+ actions:
1626
+ type: array
1627
+ items:
1628
+ $ref: '#/components/schemas/ActionDefinition'
1629
+ HealthReport:
1630
+ type: object
1631
+ required: [id, serviceId, status, reportedAt]
1632
+ properties:
1633
+ id:
1634
+ type: string
1635
+ pattern: '^hlr_'
1636
+ serviceId:
1637
+ type: string
1638
+ pattern: '^svc_'
1639
+ agentId:
1640
+ type: string
1641
+ pattern: '^agt_'
1642
+ status:
1643
+ $ref: '#/components/schemas/HealthStatus'
1644
+ message:
1645
+ type: [string, 'null']
1646
+ details:
1647
+ type: object
1648
+ additionalProperties: true
1649
+ reportedAt:
1650
+ type: string
1651
+ format: date-time
1652
+ ConfigItem:
1653
+ type: object
1654
+ required: [id, serviceId, key, type, sensitive]
1655
+ properties:
1656
+ id:
1657
+ type: string
1658
+ pattern: '^cfg_'
1659
+ serviceId:
1660
+ type: string
1661
+ pattern: '^svc_'
1662
+ key:
1663
+ type: string
1664
+ label:
1665
+ type: [string, 'null']
1666
+ type:
1667
+ type: string
1668
+ source:
1669
+ type: [string, 'null']
1670
+ editable:
1671
+ type: boolean
1672
+ sensitive:
1673
+ type: boolean
1674
+ valuePreview:
1675
+ type: [string, 'null']
1676
+ defaultValue:
1677
+ type: [string, 'null']
1678
+ secretRef:
1679
+ type: [string, 'null']
1680
+ ActionDefinition:
1681
+ type: object
1682
+ required: [id, serviceId, name, label, dangerLevel, requiresConfirmation]
1683
+ properties:
1684
+ id:
1685
+ type: string
1686
+ pattern: '^act_'
1687
+ serviceId:
1688
+ type: string
1689
+ pattern: '^svc_'
1690
+ name:
1691
+ type: string
1692
+ label:
1693
+ type: string
1694
+ description:
1695
+ type: [string, 'null']
1696
+ dangerLevel:
1697
+ $ref: '#/components/schemas/DangerLevel'
1698
+ requiresConfirmation:
1699
+ type: boolean
1700
+ inputSchema:
1701
+ type: object
1702
+ additionalProperties: true
1703
+ timeoutSeconds:
1704
+ type: integer
1705
+ CreateActionCommandRequest:
1706
+ type: object
1707
+ properties:
1708
+ payload:
1709
+ type: object
1710
+ additionalProperties: true
1711
+ confirmation:
1712
+ type: boolean
1713
+ description: |
1714
+ Required to be `true` when `ActionDefinition.requiresConfirmation`
1715
+ is true. Backend rejects with `409 ACTION_REQUIRES_CONFIRMATION`
1716
+ otherwise. UI MUST show an explicit confirmation dialog before
1717
+ sending.
1718
+ Command:
1719
+ type: object
1720
+ required: [id, agentId, serviceId, type, actionName, status, createdAt]
1721
+ properties:
1722
+ id:
1723
+ type: string
1724
+ pattern: '^cmd_'
1725
+ agentId:
1726
+ type: string
1727
+ pattern: '^agt_'
1728
+ serviceId:
1729
+ type: string
1730
+ pattern: '^svc_'
1731
+ type:
1732
+ type: string
1733
+ enum: [ACTION_PREPARE, ACTION_EXECUTE, ACTION]
1734
+ actionName:
1735
+ type: string
1736
+ status:
1737
+ $ref: '#/components/schemas/CommandStatus'
1738
+ payload:
1739
+ type: object
1740
+ additionalProperties: true
1741
+ createdByUserId:
1742
+ type: [string, 'null']
1743
+ pattern: '^usr_'
1744
+ createdAt:
1745
+ type: string
1746
+ format: date-time
1747
+ startedAt:
1748
+ type: [string, 'null']
1749
+ format: date-time
1750
+ completedAt:
1751
+ type: [string, 'null']
1752
+ format: date-time
1753
+ expiresAt:
1754
+ type: [string, 'null']
1755
+ format: date-time
1756
+ CommandDetail:
1757
+ allOf:
1758
+ - $ref: '#/components/schemas/Command'
1759
+ - type: object
1760
+ properties:
1761
+ result:
1762
+ $ref: '#/components/schemas/CommandResult'
1763
+ CommandResult:
1764
+ type: object
1765
+ required: [id, commandId, success, reportedAt]
1766
+ properties:
1767
+ id:
1768
+ type: string
1769
+ pattern: '^crs_'
1770
+ commandId:
1771
+ type: string
1772
+ pattern: '^cmd_'
1773
+ success:
1774
+ type: boolean
1775
+ message:
1776
+ type: [string, 'null']
1777
+ data:
1778
+ type: object
1779
+ additionalProperties: true
1780
+ error:
1781
+ type: object
1782
+ additionalProperties: true
1783
+ reportedAt:
1784
+ type: string
1785
+ format: date-time
1786
+ ReportCommandResultRequest:
1787
+ type: object
1788
+ required: [success]
1789
+ properties:
1790
+ success:
1791
+ type: boolean
1792
+ message:
1793
+ type: string
1794
+ data:
1795
+ type: object
1796
+ additionalProperties: true
1797
+ error:
1798
+ type: object
1799
+ additionalProperties: true
1800
+ description: |
1801
+ Structured error payload. SHOULD include at minimum
1802
+ `{ code: string, message: string }`. Backend stores it as JSON.
1803
+ startedAt:
1804
+ type: string
1805
+ format: date-time
1806
+ description: When the Agent began executing the action handler.
1807
+ finishedAt:
1808
+ type: string
1809
+ format: date-time
1810
+ description: When the Agent finished executing. Defaults to receipt time.
1811
+ RegisterAgentRequest:
1812
+ type: object
1813
+ required: [registrationToken, agent]
1814
+ properties:
1815
+ registrationToken:
1816
+ type: string
1817
+ pattern: '^opstage_reg_'
1818
+ agent:
1819
+ type: object
1820
+ required: [code, mode]
1821
+ properties:
1822
+ code:
1823
+ type: string
1824
+ name:
1825
+ type: string
1826
+ mode:
1827
+ type: string
1828
+ enum: [embedded]
1829
+ runtime:
1830
+ type: string
1831
+ service:
1832
+ $ref: '#/components/schemas/ReportedService'
1833
+ RegisterAgentResponse:
1834
+ type: object
1835
+ required: [agentId, agentToken, heartbeatIntervalSeconds, commandPollIntervalSeconds]
1836
+ properties:
1837
+ agentId:
1838
+ type: string
1839
+ pattern: '^agt_'
1840
+ agentToken:
1841
+ type: string
1842
+ pattern: '^opstage_agent_'
1843
+ description: Raw Agent token shown only once to the Agent.
1844
+ heartbeatIntervalSeconds:
1845
+ type: integer
1846
+ commandPollIntervalSeconds:
1847
+ type: integer
1848
+ AgentHeartbeatRequest:
1849
+ type: object
1850
+ properties:
1851
+ serviceId:
1852
+ type: string
1853
+ pattern: '^svc_'
1854
+ health:
1855
+ $ref: '#/components/schemas/HealthReportInput'
1856
+ AgentHeartbeatResponse:
1857
+ type: object
1858
+ required: [heartbeatIntervalSeconds, commandPollIntervalSeconds]
1859
+ properties:
1860
+ heartbeatIntervalSeconds:
1861
+ type: integer
1862
+ commandPollIntervalSeconds:
1863
+ type: integer
1864
+ ServiceReportRequest:
1865
+ type: object
1866
+ required: [services]
1867
+ properties:
1868
+ services:
1869
+ type: array
1870
+ items:
1871
+ $ref: '#/components/schemas/ReportedService'
1872
+ ReportedService:
1873
+ type: object
1874
+ required: [code, name, manifest]
1875
+ properties:
1876
+ code:
1877
+ type: string
1878
+ minLength: 1
1879
+ maxLength: 80
1880
+ pattern: '^[a-z0-9][a-z0-9_-]*$'
1881
+ name:
1882
+ type: string
1883
+ minLength: 1
1884
+ maxLength: 200
1885
+ description:
1886
+ type: string
1887
+ maxLength: 2000
1888
+ version:
1889
+ type: string
1890
+ maxLength: 80
1891
+ runtime:
1892
+ type: string
1893
+ enum: [nodejs, java, python, go, other]
1894
+ manifest:
1895
+ $ref: '#/components/schemas/CapsuleManifest'
1896
+ health:
1897
+ $ref: '#/components/schemas/HealthReportInput'
1898
+ configs:
1899
+ type: array
1900
+ items:
1901
+ $ref: '#/components/schemas/ConfigItemInput'
1902
+ actions:
1903
+ type: array
1904
+ items:
1905
+ $ref: '#/components/schemas/ActionDefinitionInput'
1906
+ CapsuleManifest:
1907
+ type: object
1908
+ description: |
1909
+ Authoritative shape of a Capsule Service manifest reported by the Agent.
1910
+ Backend stores the full object as `CapsuleService.manifestJson` and validates
1911
+ the listed required fields. Unknown fields are preserved (forward-compat).
1912
+ required: [kind, code, name, version, runtime, agentMode]
1913
+ properties:
1914
+ kind:
1915
+ type: string
1916
+ const: CapsuleService
1917
+ schemaVersion:
1918
+ type: string
1919
+ description: Manifest schema version. Defaults to "1.0" if omitted.
1920
+ default: "1.0"
1921
+ code:
1922
+ type: string
1923
+ minLength: 1
1924
+ maxLength: 80
1925
+ pattern: '^[a-z0-9][a-z0-9_-]*$'
1926
+ name:
1927
+ type: string
1928
+ minLength: 1
1929
+ maxLength: 200
1930
+ description:
1931
+ type: string
1932
+ maxLength: 2000
1933
+ version:
1934
+ type: string
1935
+ minLength: 1
1936
+ maxLength: 80
1937
+ runtime:
1938
+ type: string
1939
+ enum: [nodejs, java, python, go, other]
1940
+ agentMode:
1941
+ type: string
1942
+ enum: [embedded, sidecar, external]
1943
+ capabilities:
1944
+ type: array
1945
+ items:
1946
+ type: string
1947
+ labels:
1948
+ type: object
1949
+ additionalProperties:
1950
+ type: string
1951
+ additionalProperties: true
1952
+ HealthReportInput:
1953
+ type: object
1954
+ required: [status]
1955
+ properties:
1956
+ status:
1957
+ $ref: '#/components/schemas/HealthStatus'
1958
+ message:
1959
+ type: string
1960
+ details:
1961
+ type: object
1962
+ additionalProperties: true
1963
+ ConfigItemInput:
1964
+ type: object
1965
+ required: [key, type]
1966
+ properties:
1967
+ key:
1968
+ type: string
1969
+ label:
1970
+ type: string
1971
+ type:
1972
+ type: string
1973
+ source:
1974
+ type: string
1975
+ editable:
1976
+ type: boolean
1977
+ sensitive:
1978
+ type: boolean
1979
+ valuePreview:
1980
+ type: string
1981
+ defaultValue:
1982
+ type: string
1983
+ secretRef:
1984
+ type: string
1985
+ ActionDefinitionInput:
1986
+ type: object
1987
+ required: [name, label]
1988
+ properties:
1989
+ name:
1990
+ type: string
1991
+ label:
1992
+ type: string
1993
+ description:
1994
+ type: string
1995
+ dangerLevel:
1996
+ $ref: '#/components/schemas/DangerLevel'
1997
+ requiresConfirmation:
1998
+ type: boolean
1999
+ inputSchema:
2000
+ type: object
2001
+ additionalProperties: true
2002
+ timeoutSeconds:
2003
+ type: integer
2004
+ AuditEvent:
2005
+ type: object
2006
+ required: [id, actorType, action, result, createdAt]
2007
+ properties:
2008
+ id:
2009
+ type: string
2010
+ pattern: '^aud_'
2011
+ actorType:
2012
+ $ref: '#/components/schemas/AuditActorType'
2013
+ actorId:
2014
+ type: [string, 'null']
2015
+ action:
2016
+ type: string
2017
+ targetType:
2018
+ type: [string, 'null']
2019
+ targetId:
2020
+ type: [string, 'null']
2021
+ result:
2022
+ $ref: '#/components/schemas/AuditResult'
2023
+ message:
2024
+ type: [string, 'null']
2025
+ metadata:
2026
+ type: object
2027
+ additionalProperties: true
2028
+ createdAt:
2029
+ type: string
2030
+ format: date-time
2031
+ SystemHealth:
2032
+ type: object
2033
+ required: [status, timestamp, version, edition, database]
2034
+ properties:
2035
+ status:
2036
+ type: string
2037
+ description: Overall Backend status. DOWN when any critical dependency is unreachable.
2038
+ enum: [UP, DEGRADED, DOWN]
2039
+ timestamp:
2040
+ type: string
2041
+ format: date-time
2042
+ description: Backend server clock at the time of the response.
2043
+ version:
2044
+ type: string
2045
+ description: Backend semantic version (matches package.json version).
2046
+ edition:
2047
+ type: string
2048
+ description: Build edition. Always "ce" for CE v0.1.
2049
+ enum: [ce]
2050
+ database:
2051
+ type: object
2052
+ required: [status]
2053
+ properties:
2054
+ status:
2055
+ type: string
2056
+ enum: [UP, DEGRADED, DOWN]
2057
+ kind:
2058
+ type: string
2059
+ enum: [sqlite, postgres, mysql]
2060
+ description: Database driver in use. Always "sqlite" in CE v0.1.
2061
+ latencyMs:
2062
+ type: integer
2063
+ minimum: 0
2064
+ description: Round-trip time of the health probe query (e.g. SELECT 1).
2065
+ uptimeSeconds:
2066
+ type: integer
2067
+ minimum: 0
2068
+ description: Seconds since the Backend process started.
2069
+ SystemVersion:
2070
+ type: object
2071
+ required: [version, edition]
2072
+ properties:
2073
+ version:
2074
+ type: string
2075
+ edition:
2076
+ type: string
2077
+ enum: [ce]
2078
+ commit:
2079
+ type: string
2080
+ description: Optional git commit short SHA when available at build time.
2081
+ buildTime:
2082
+ type: string
2083
+ format: date-time
2084
+ description: Optional ISO-8601 timestamp captured at build time.
2085
+ enum: [CE]