@willyim/idp 0.2.0 → 0.3.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.
@@ -3,7 +3,7 @@
3
3
  "info": {
4
4
  "title": "willy.im IdP — Management API",
5
5
  "version": "1.0.0",
6
- "description": "Management API for the willy.im identity provider. Authenticate with `Authorization: Bearer <token>`. Two kinds of token: the superadmin `ADMIN_API_TOKEN` (every app) and per-app **scoped API keys** minted in the admin console (one app, a fixed permission set). The cross-app list endpoints below require the superadmin token. Application registration is done in the admin console (better-auth ties client creation to an admin session)."
6
+ "description": "Management API for the willy.im identity provider. Authenticate with `Authorization: Bearer <token>`. Two kinds of token: the superadmin `ADMIN_API_TOKEN` (every app) and per-app **scoped API keys** minted in the admin console (one app, a fixed permission set). The cross-app endpoints below require the superadmin token."
7
7
  },
8
8
  "servers": [
9
9
  {
@@ -67,6 +67,17 @@
67
67
  ],
68
68
  "description": "Application key; consumer workspace claims are filtered by this"
69
69
  },
70
+ "allowSignup": {
71
+ "type": "boolean",
72
+ "description": "Whether unknown users may sign themselves up"
73
+ },
74
+ "permissions": {
75
+ "type": "array",
76
+ "items": {
77
+ "type": "string"
78
+ },
79
+ "description": "The app's declared product-permission catalog"
80
+ },
70
81
  "redirectUris": {
71
82
  "type": "array",
72
83
  "items": {
@@ -85,6 +96,8 @@
85
96
  "clientId",
86
97
  "name",
87
98
  "app",
99
+ "allowSignup",
100
+ "permissions",
88
101
  "redirectUris",
89
102
  "disabled",
90
103
  "createdAt"
@@ -105,6 +118,1089 @@
105
118
  "description": "Missing or invalid bearer token"
106
119
  }
107
120
  }
121
+ },
122
+ "post": {
123
+ "summary": "Register an application (client secret returned once)",
124
+ "description": "Requires the superadmin token. Creating an application is an IdP-level act — there is no app to scope a permission to yet.",
125
+ "security": [
126
+ {
127
+ "bearerAuth": []
128
+ }
129
+ ],
130
+ "requestBody": {
131
+ "required": true,
132
+ "content": {
133
+ "application/json": {
134
+ "schema": {
135
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
136
+ "type": "object",
137
+ "properties": {
138
+ "name": {
139
+ "type": "string",
140
+ "minLength": 1
141
+ },
142
+ "app": {
143
+ "type": "string",
144
+ "minLength": 1,
145
+ "pattern": "^[a-z0-9][a-z0-9-]*$",
146
+ "description": "Stable application key — the join key for members, workspaces and keys"
147
+ },
148
+ "redirectUris": {
149
+ "minItems": 1,
150
+ "type": "array",
151
+ "items": {
152
+ "type": "string",
153
+ "minLength": 1
154
+ }
155
+ },
156
+ "firstAdminUserId": {
157
+ "description": "Who becomes the app's first IdP admin. Defaults to the calling user; null leaves the app with no members (superadmin-managed).",
158
+ "anyOf": [
159
+ {
160
+ "type": "string"
161
+ },
162
+ {
163
+ "type": "null"
164
+ }
165
+ ]
166
+ }
167
+ },
168
+ "required": [
169
+ "name",
170
+ "app",
171
+ "redirectUris"
172
+ ]
173
+ }
174
+ }
175
+ }
176
+ },
177
+ "responses": {
178
+ "201": {
179
+ "description": "OK",
180
+ "content": {
181
+ "application/json": {
182
+ "schema": {
183
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
184
+ "type": "object",
185
+ "properties": {
186
+ "clientId": {
187
+ "type": "string"
188
+ },
189
+ "clientSecret": {
190
+ "type": "string",
191
+ "description": "Plaintext client secret — shown exactly once, never stored"
192
+ },
193
+ "app": {
194
+ "type": "string"
195
+ }
196
+ },
197
+ "required": [
198
+ "clientId",
199
+ "clientSecret",
200
+ "app"
201
+ ],
202
+ "additionalProperties": false
203
+ }
204
+ }
205
+ }
206
+ },
207
+ "401": {
208
+ "description": "Missing or invalid bearer token"
209
+ },
210
+ "405": {
211
+ "description": "Method not allowed on this resource (see the `Allow` header)"
212
+ },
213
+ "409": {
214
+ "description": "Conflict (already a member, last admin, slug taken, …)"
215
+ },
216
+ "422": {
217
+ "description": "Body failed validation"
218
+ }
219
+ }
220
+ }
221
+ },
222
+ "/api/v1/applications/{clientId}": {
223
+ "get": {
224
+ "summary": "Get one application",
225
+ "description": "Requires `app:read` on the path app (or the superadmin token).",
226
+ "security": [
227
+ {
228
+ "bearerAuth": []
229
+ }
230
+ ],
231
+ "parameters": [
232
+ {
233
+ "name": "clientId",
234
+ "in": "path",
235
+ "required": true,
236
+ "description": "OAuth client id of the application.",
237
+ "schema": {
238
+ "type": "string"
239
+ }
240
+ }
241
+ ],
242
+ "responses": {
243
+ "200": {
244
+ "description": "OK",
245
+ "content": {
246
+ "application/json": {
247
+ "schema": {
248
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
249
+ "type": "object",
250
+ "properties": {
251
+ "clientId": {
252
+ "type": "string"
253
+ },
254
+ "name": {
255
+ "anyOf": [
256
+ {
257
+ "type": "string"
258
+ },
259
+ {
260
+ "type": "null"
261
+ }
262
+ ]
263
+ },
264
+ "app": {
265
+ "anyOf": [
266
+ {
267
+ "type": "string"
268
+ },
269
+ {
270
+ "type": "null"
271
+ }
272
+ ],
273
+ "description": "Application key; consumer workspace claims are filtered by this"
274
+ },
275
+ "allowSignup": {
276
+ "type": "boolean",
277
+ "description": "Whether unknown users may sign themselves up"
278
+ },
279
+ "permissions": {
280
+ "type": "array",
281
+ "items": {
282
+ "type": "string"
283
+ },
284
+ "description": "The app's declared product-permission catalog"
285
+ },
286
+ "redirectUris": {
287
+ "type": "array",
288
+ "items": {
289
+ "type": "string"
290
+ }
291
+ },
292
+ "disabled": {
293
+ "type": "boolean"
294
+ },
295
+ "createdAt": {
296
+ "type": "string",
297
+ "description": "ISO 8601 timestamp"
298
+ }
299
+ },
300
+ "required": [
301
+ "clientId",
302
+ "name",
303
+ "app",
304
+ "allowSignup",
305
+ "permissions",
306
+ "redirectUris",
307
+ "disabled",
308
+ "createdAt"
309
+ ],
310
+ "additionalProperties": false
311
+ }
312
+ }
313
+ }
314
+ },
315
+ "401": {
316
+ "description": "Missing or invalid bearer token"
317
+ },
318
+ "403": {
319
+ "description": "Key lacks the permission / is bound to another app"
320
+ },
321
+ "404": {
322
+ "description": "No application with that client id"
323
+ }
324
+ }
325
+ },
326
+ "patch": {
327
+ "summary": "Update an application",
328
+ "description": "Requires `app:update` on the path app (or the superadmin token).",
329
+ "security": [
330
+ {
331
+ "bearerAuth": []
332
+ }
333
+ ],
334
+ "parameters": [
335
+ {
336
+ "name": "clientId",
337
+ "in": "path",
338
+ "required": true,
339
+ "description": "OAuth client id of the application.",
340
+ "schema": {
341
+ "type": "string"
342
+ }
343
+ }
344
+ ],
345
+ "requestBody": {
346
+ "required": true,
347
+ "content": {
348
+ "application/json": {
349
+ "schema": {
350
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
351
+ "type": "object",
352
+ "properties": {
353
+ "name": {
354
+ "type": "string",
355
+ "minLength": 1
356
+ },
357
+ "redirectUris": {
358
+ "minItems": 1,
359
+ "type": "array",
360
+ "items": {
361
+ "type": "string",
362
+ "minLength": 1
363
+ }
364
+ },
365
+ "allowSignup": {
366
+ "type": "boolean"
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ },
373
+ "responses": {
374
+ "200": {
375
+ "description": "OK",
376
+ "content": {
377
+ "application/json": {
378
+ "schema": {
379
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
380
+ "type": "object",
381
+ "properties": {
382
+ "clientId": {
383
+ "type": "string"
384
+ },
385
+ "name": {
386
+ "anyOf": [
387
+ {
388
+ "type": "string"
389
+ },
390
+ {
391
+ "type": "null"
392
+ }
393
+ ]
394
+ },
395
+ "app": {
396
+ "anyOf": [
397
+ {
398
+ "type": "string"
399
+ },
400
+ {
401
+ "type": "null"
402
+ }
403
+ ],
404
+ "description": "Application key; consumer workspace claims are filtered by this"
405
+ },
406
+ "allowSignup": {
407
+ "type": "boolean",
408
+ "description": "Whether unknown users may sign themselves up"
409
+ },
410
+ "permissions": {
411
+ "type": "array",
412
+ "items": {
413
+ "type": "string"
414
+ },
415
+ "description": "The app's declared product-permission catalog"
416
+ },
417
+ "redirectUris": {
418
+ "type": "array",
419
+ "items": {
420
+ "type": "string"
421
+ }
422
+ },
423
+ "disabled": {
424
+ "type": "boolean"
425
+ },
426
+ "createdAt": {
427
+ "type": "string",
428
+ "description": "ISO 8601 timestamp"
429
+ }
430
+ },
431
+ "required": [
432
+ "clientId",
433
+ "name",
434
+ "app",
435
+ "allowSignup",
436
+ "permissions",
437
+ "redirectUris",
438
+ "disabled",
439
+ "createdAt"
440
+ ],
441
+ "additionalProperties": false
442
+ }
443
+ }
444
+ }
445
+ },
446
+ "401": {
447
+ "description": "Missing or invalid bearer token"
448
+ },
449
+ "403": {
450
+ "description": "Key lacks the permission / is bound to another app"
451
+ },
452
+ "404": {
453
+ "description": "No application with that client id"
454
+ },
455
+ "405": {
456
+ "description": "Method not allowed on this resource (see the `Allow` header)"
457
+ },
458
+ "409": {
459
+ "description": "Conflict (already a member, last admin, slug taken, …)"
460
+ },
461
+ "422": {
462
+ "description": "Body failed validation"
463
+ }
464
+ }
465
+ },
466
+ "delete": {
467
+ "summary": "Deregister an application",
468
+ "description": "Requires `app:delete` on the path app (or the superadmin token).",
469
+ "security": [
470
+ {
471
+ "bearerAuth": []
472
+ }
473
+ ],
474
+ "parameters": [
475
+ {
476
+ "name": "clientId",
477
+ "in": "path",
478
+ "required": true,
479
+ "description": "OAuth client id of the application.",
480
+ "schema": {
481
+ "type": "string"
482
+ }
483
+ }
484
+ ],
485
+ "responses": {
486
+ "200": {
487
+ "description": "OK",
488
+ "content": {
489
+ "application/json": {
490
+ "schema": {
491
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
492
+ "type": "object",
493
+ "properties": {
494
+ "ok": {
495
+ "type": "boolean",
496
+ "const": true
497
+ }
498
+ },
499
+ "required": [
500
+ "ok"
501
+ ],
502
+ "additionalProperties": false
503
+ }
504
+ }
505
+ }
506
+ },
507
+ "401": {
508
+ "description": "Missing or invalid bearer token"
509
+ },
510
+ "403": {
511
+ "description": "Key lacks the permission / is bound to another app"
512
+ },
513
+ "404": {
514
+ "description": "No application with that client id"
515
+ },
516
+ "405": {
517
+ "description": "Method not allowed on this resource (see the `Allow` header)"
518
+ },
519
+ "409": {
520
+ "description": "Conflict (already a member, last admin, slug taken, …)"
521
+ }
522
+ }
523
+ }
524
+ },
525
+ "/api/v1/applications/{clientId}/rotate-secret": {
526
+ "post": {
527
+ "summary": "Rotate the client secret (returned once; the old one stops working)",
528
+ "description": "Requires `app:update` on the path app (or the superadmin token).",
529
+ "security": [
530
+ {
531
+ "bearerAuth": []
532
+ }
533
+ ],
534
+ "parameters": [
535
+ {
536
+ "name": "clientId",
537
+ "in": "path",
538
+ "required": true,
539
+ "description": "OAuth client id of the application.",
540
+ "schema": {
541
+ "type": "string"
542
+ }
543
+ }
544
+ ],
545
+ "responses": {
546
+ "200": {
547
+ "description": "OK",
548
+ "content": {
549
+ "application/json": {
550
+ "schema": {
551
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
552
+ "type": "object",
553
+ "properties": {
554
+ "clientSecret": {
555
+ "type": "string",
556
+ "description": "Plaintext client secret — shown exactly once, never stored"
557
+ }
558
+ },
559
+ "required": [
560
+ "clientSecret"
561
+ ],
562
+ "additionalProperties": false
563
+ }
564
+ }
565
+ }
566
+ },
567
+ "401": {
568
+ "description": "Missing or invalid bearer token"
569
+ },
570
+ "403": {
571
+ "description": "Key lacks the permission / is bound to another app"
572
+ },
573
+ "404": {
574
+ "description": "No application with that client id"
575
+ },
576
+ "405": {
577
+ "description": "Method not allowed on this resource (see the `Allow` header)"
578
+ },
579
+ "409": {
580
+ "description": "Conflict (already a member, last admin, slug taken, …)"
581
+ }
582
+ }
583
+ }
584
+ },
585
+ "/api/v1/apps/{app}/permissions": {
586
+ "put": {
587
+ "summary": "Replace the app's product-permission catalog",
588
+ "description": "Requires `app:update` on the path app (or the superadmin token).",
589
+ "security": [
590
+ {
591
+ "bearerAuth": []
592
+ }
593
+ ],
594
+ "parameters": [
595
+ {
596
+ "name": "app",
597
+ "in": "path",
598
+ "required": true,
599
+ "description": "Application key (oauth_client.metadata.app).",
600
+ "schema": {
601
+ "type": "string"
602
+ }
603
+ }
604
+ ],
605
+ "requestBody": {
606
+ "required": true,
607
+ "content": {
608
+ "application/json": {
609
+ "schema": {
610
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
611
+ "type": "object",
612
+ "properties": {
613
+ "permissions": {
614
+ "type": "array",
615
+ "items": {
616
+ "type": "string",
617
+ "minLength": 1
618
+ }
619
+ }
620
+ },
621
+ "required": [
622
+ "permissions"
623
+ ]
624
+ }
625
+ }
626
+ }
627
+ },
628
+ "responses": {
629
+ "200": {
630
+ "description": "OK",
631
+ "content": {
632
+ "application/json": {
633
+ "schema": {
634
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
635
+ "type": "object",
636
+ "properties": {
637
+ "permissions": {
638
+ "type": "array",
639
+ "items": {
640
+ "type": "string"
641
+ }
642
+ }
643
+ },
644
+ "required": [
645
+ "permissions"
646
+ ],
647
+ "additionalProperties": false
648
+ }
649
+ }
650
+ }
651
+ },
652
+ "401": {
653
+ "description": "Missing or invalid bearer token"
654
+ },
655
+ "403": {
656
+ "description": "Key lacks the permission / is bound to another app"
657
+ },
658
+ "404": {
659
+ "description": "No application with that app key"
660
+ },
661
+ "405": {
662
+ "description": "Method not allowed on this resource (see the `Allow` header)"
663
+ },
664
+ "409": {
665
+ "description": "Conflict (already a member, last admin, slug taken, …)"
666
+ },
667
+ "422": {
668
+ "description": "Body failed validation"
669
+ }
670
+ }
671
+ }
672
+ },
673
+ "/api/v1/apps/{app}/keys": {
674
+ "get": {
675
+ "summary": "List scoped management API keys (never the hashes)",
676
+ "description": "Requires `apikey:read` on the path app (or the superadmin token).",
677
+ "security": [
678
+ {
679
+ "bearerAuth": []
680
+ }
681
+ ],
682
+ "parameters": [
683
+ {
684
+ "name": "app",
685
+ "in": "path",
686
+ "required": true,
687
+ "description": "Application key (oauth_client.metadata.app).",
688
+ "schema": {
689
+ "type": "string"
690
+ }
691
+ }
692
+ ],
693
+ "responses": {
694
+ "200": {
695
+ "description": "OK",
696
+ "content": {
697
+ "application/json": {
698
+ "schema": {
699
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
700
+ "type": "object",
701
+ "properties": {
702
+ "keys": {
703
+ "type": "array",
704
+ "items": {
705
+ "type": "object",
706
+ "properties": {
707
+ "id": {
708
+ "type": "string"
709
+ },
710
+ "name": {
711
+ "type": "string"
712
+ },
713
+ "prefix": {
714
+ "type": "string",
715
+ "description": "Non-secret token prefix (wim_…) for display"
716
+ },
717
+ "permissions": {
718
+ "type": "array",
719
+ "items": {
720
+ "type": "string"
721
+ },
722
+ "description": "IdP management permissions this key holds"
723
+ },
724
+ "status": {
725
+ "type": "string",
726
+ "enum": [
727
+ "active",
728
+ "expired",
729
+ "revoked"
730
+ ]
731
+ },
732
+ "createdAt": {
733
+ "type": "string"
734
+ },
735
+ "lastUsedAt": {
736
+ "anyOf": [
737
+ {
738
+ "type": "string"
739
+ },
740
+ {
741
+ "type": "null"
742
+ }
743
+ ]
744
+ },
745
+ "expiresAt": {
746
+ "anyOf": [
747
+ {
748
+ "type": "string"
749
+ },
750
+ {
751
+ "type": "null"
752
+ }
753
+ ]
754
+ },
755
+ "revokedAt": {
756
+ "anyOf": [
757
+ {
758
+ "type": "string"
759
+ },
760
+ {
761
+ "type": "null"
762
+ }
763
+ ]
764
+ }
765
+ },
766
+ "required": [
767
+ "id",
768
+ "name",
769
+ "prefix",
770
+ "permissions",
771
+ "status",
772
+ "createdAt",
773
+ "lastUsedAt",
774
+ "expiresAt",
775
+ "revokedAt"
776
+ ],
777
+ "additionalProperties": false
778
+ }
779
+ }
780
+ },
781
+ "required": [
782
+ "keys"
783
+ ],
784
+ "additionalProperties": false
785
+ }
786
+ }
787
+ }
788
+ },
789
+ "401": {
790
+ "description": "Missing or invalid bearer token"
791
+ },
792
+ "403": {
793
+ "description": "Key lacks the permission / is bound to another app"
794
+ }
795
+ }
796
+ },
797
+ "post": {
798
+ "summary": "Mint a scoped management API key (plaintext returned once)",
799
+ "description": "Requires `apikey:create` on the path app (or the superadmin token). The requested permissions must be a subset of the caller's own, otherwise 403 `permissions_exceed_caller` — without that rule any key holding `apikey:create` could mint itself a more powerful successor.",
800
+ "security": [
801
+ {
802
+ "bearerAuth": []
803
+ }
804
+ ],
805
+ "parameters": [
806
+ {
807
+ "name": "app",
808
+ "in": "path",
809
+ "required": true,
810
+ "description": "Application key (oauth_client.metadata.app).",
811
+ "schema": {
812
+ "type": "string"
813
+ }
814
+ }
815
+ ],
816
+ "requestBody": {
817
+ "required": true,
818
+ "content": {
819
+ "application/json": {
820
+ "schema": {
821
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
822
+ "type": "object",
823
+ "properties": {
824
+ "name": {
825
+ "type": "string",
826
+ "minLength": 1
827
+ },
828
+ "permissions": {
829
+ "type": "array",
830
+ "items": {
831
+ "type": "string",
832
+ "minLength": 1
833
+ },
834
+ "description": "IdP management permissions; must be a subset of the caller's own on this app"
835
+ },
836
+ "expiresAt": {
837
+ "description": "ISO 8601; omit for non-expiring",
838
+ "type": "string",
839
+ "format": "date-time",
840
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
841
+ }
842
+ },
843
+ "required": [
844
+ "name",
845
+ "permissions"
846
+ ]
847
+ }
848
+ }
849
+ }
850
+ },
851
+ "responses": {
852
+ "201": {
853
+ "description": "OK",
854
+ "content": {
855
+ "application/json": {
856
+ "schema": {
857
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
858
+ "type": "object",
859
+ "properties": {
860
+ "id": {
861
+ "type": "string"
862
+ },
863
+ "token": {
864
+ "type": "string",
865
+ "description": "Plaintext key — shown exactly once, never stored"
866
+ },
867
+ "prefix": {
868
+ "type": "string"
869
+ }
870
+ },
871
+ "required": [
872
+ "id",
873
+ "token",
874
+ "prefix"
875
+ ],
876
+ "additionalProperties": false
877
+ }
878
+ }
879
+ }
880
+ },
881
+ "401": {
882
+ "description": "Missing or invalid bearer token"
883
+ },
884
+ "403": {
885
+ "description": "Key lacks the permission / is bound to another app"
886
+ },
887
+ "405": {
888
+ "description": "Method not allowed on this resource (see the `Allow` header)"
889
+ },
890
+ "409": {
891
+ "description": "Conflict (already a member, last admin, slug taken, …)"
892
+ },
893
+ "422": {
894
+ "description": "Body failed validation"
895
+ }
896
+ }
897
+ }
898
+ },
899
+ "/api/v1/apps/{app}/keys/{id}": {
900
+ "delete": {
901
+ "summary": "Revoke a scoped management API key (idempotent)",
902
+ "description": "Requires `apikey:revoke` on the path app (or the superadmin token).",
903
+ "security": [
904
+ {
905
+ "bearerAuth": []
906
+ }
907
+ ],
908
+ "parameters": [
909
+ {
910
+ "name": "app",
911
+ "in": "path",
912
+ "required": true,
913
+ "description": "Application key (oauth_client.metadata.app).",
914
+ "schema": {
915
+ "type": "string"
916
+ }
917
+ },
918
+ {
919
+ "name": "id",
920
+ "in": "path",
921
+ "required": true,
922
+ "schema": {
923
+ "type": "string"
924
+ }
925
+ }
926
+ ],
927
+ "responses": {
928
+ "200": {
929
+ "description": "OK",
930
+ "content": {
931
+ "application/json": {
932
+ "schema": {
933
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
934
+ "type": "object",
935
+ "properties": {
936
+ "ok": {
937
+ "type": "boolean",
938
+ "const": true
939
+ }
940
+ },
941
+ "required": [
942
+ "ok"
943
+ ],
944
+ "additionalProperties": false
945
+ }
946
+ }
947
+ }
948
+ },
949
+ "401": {
950
+ "description": "Missing or invalid bearer token"
951
+ },
952
+ "403": {
953
+ "description": "Key lacks the permission / is bound to another app"
954
+ },
955
+ "404": {
956
+ "description": "No key with that id on this app"
957
+ },
958
+ "405": {
959
+ "description": "Method not allowed on this resource (see the `Allow` header)"
960
+ },
961
+ "409": {
962
+ "description": "Conflict (already a member, last admin, slug taken, …)"
963
+ }
964
+ }
965
+ }
966
+ },
967
+ "/api/v1/admin-keys": {
968
+ "get": {
969
+ "summary": "List IdP-level admin keys (never the hashes)",
970
+ "description": "Requires the superadmin token or an admin key. Admin keys are `api_key` rows with no application scope, so they hold every permission on every app.",
971
+ "security": [
972
+ {
973
+ "bearerAuth": []
974
+ }
975
+ ],
976
+ "responses": {
977
+ "200": {
978
+ "description": "OK",
979
+ "content": {
980
+ "application/json": {
981
+ "schema": {
982
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
983
+ "type": "object",
984
+ "properties": {
985
+ "keys": {
986
+ "type": "array",
987
+ "items": {
988
+ "type": "object",
989
+ "properties": {
990
+ "id": {
991
+ "type": "string"
992
+ },
993
+ "name": {
994
+ "type": "string"
995
+ },
996
+ "prefix": {
997
+ "type": "string",
998
+ "description": "Non-secret token prefix (wim_…) for display"
999
+ },
1000
+ "status": {
1001
+ "type": "string",
1002
+ "enum": [
1003
+ "active",
1004
+ "expired",
1005
+ "revoked"
1006
+ ]
1007
+ },
1008
+ "createdAt": {
1009
+ "type": "string"
1010
+ },
1011
+ "lastUsedAt": {
1012
+ "anyOf": [
1013
+ {
1014
+ "type": "string"
1015
+ },
1016
+ {
1017
+ "type": "null"
1018
+ }
1019
+ ]
1020
+ },
1021
+ "expiresAt": {
1022
+ "anyOf": [
1023
+ {
1024
+ "type": "string"
1025
+ },
1026
+ {
1027
+ "type": "null"
1028
+ }
1029
+ ]
1030
+ },
1031
+ "revokedAt": {
1032
+ "anyOf": [
1033
+ {
1034
+ "type": "string"
1035
+ },
1036
+ {
1037
+ "type": "null"
1038
+ }
1039
+ ]
1040
+ }
1041
+ },
1042
+ "required": [
1043
+ "id",
1044
+ "name",
1045
+ "prefix",
1046
+ "status",
1047
+ "createdAt",
1048
+ "lastUsedAt",
1049
+ "expiresAt",
1050
+ "revokedAt"
1051
+ ],
1052
+ "additionalProperties": false
1053
+ }
1054
+ }
1055
+ },
1056
+ "required": [
1057
+ "keys"
1058
+ ],
1059
+ "additionalProperties": false
1060
+ }
1061
+ }
1062
+ }
1063
+ },
1064
+ "401": {
1065
+ "description": "Missing or invalid bearer token"
1066
+ }
1067
+ }
1068
+ },
1069
+ "post": {
1070
+ "summary": "Mint an IdP-level admin key (plaintext returned once)",
1071
+ "description": "Requires the superadmin token or an admin key. Mint one per agent: unlike the static ADMIN_API_TOKEN, an admin key has a name, an optional expiry, a revoke switch, and its own `adminkey:<id>` identity in the audit log.",
1072
+ "security": [
1073
+ {
1074
+ "bearerAuth": []
1075
+ }
1076
+ ],
1077
+ "requestBody": {
1078
+ "required": true,
1079
+ "content": {
1080
+ "application/json": {
1081
+ "schema": {
1082
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1083
+ "type": "object",
1084
+ "properties": {
1085
+ "name": {
1086
+ "type": "string",
1087
+ "minLength": 1,
1088
+ "description": "Who holds it — one key per agent, so the audit log reads"
1089
+ },
1090
+ "expiresAt": {
1091
+ "description": "ISO 8601; omit for non-expiring",
1092
+ "type": "string",
1093
+ "format": "date-time",
1094
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
1095
+ }
1096
+ },
1097
+ "required": [
1098
+ "name"
1099
+ ]
1100
+ }
1101
+ }
1102
+ }
1103
+ },
1104
+ "responses": {
1105
+ "201": {
1106
+ "description": "OK",
1107
+ "content": {
1108
+ "application/json": {
1109
+ "schema": {
1110
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1111
+ "type": "object",
1112
+ "properties": {
1113
+ "id": {
1114
+ "type": "string"
1115
+ },
1116
+ "token": {
1117
+ "type": "string",
1118
+ "description": "Plaintext key — shown exactly once, never stored"
1119
+ },
1120
+ "prefix": {
1121
+ "type": "string"
1122
+ }
1123
+ },
1124
+ "required": [
1125
+ "id",
1126
+ "token",
1127
+ "prefix"
1128
+ ],
1129
+ "additionalProperties": false
1130
+ }
1131
+ }
1132
+ }
1133
+ },
1134
+ "401": {
1135
+ "description": "Missing or invalid bearer token"
1136
+ },
1137
+ "405": {
1138
+ "description": "Method not allowed on this resource (see the `Allow` header)"
1139
+ },
1140
+ "409": {
1141
+ "description": "Conflict (already a member, last admin, slug taken, …)"
1142
+ },
1143
+ "422": {
1144
+ "description": "Body failed validation"
1145
+ }
1146
+ }
1147
+ }
1148
+ },
1149
+ "/api/v1/admin-keys/{id}": {
1150
+ "delete": {
1151
+ "summary": "Revoke an IdP-level admin key (idempotent)",
1152
+ "description": "A key may revoke itself — an agent cleaning up after itself is legitimate — after which its next request is simply unauthorized.",
1153
+ "security": [
1154
+ {
1155
+ "bearerAuth": []
1156
+ }
1157
+ ],
1158
+ "parameters": [
1159
+ {
1160
+ "name": "id",
1161
+ "in": "path",
1162
+ "required": true,
1163
+ "description": "Admin key id.",
1164
+ "schema": {
1165
+ "type": "string"
1166
+ }
1167
+ }
1168
+ ],
1169
+ "responses": {
1170
+ "200": {
1171
+ "description": "OK",
1172
+ "content": {
1173
+ "application/json": {
1174
+ "schema": {
1175
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1176
+ "type": "object",
1177
+ "properties": {
1178
+ "ok": {
1179
+ "type": "boolean",
1180
+ "const": true
1181
+ }
1182
+ },
1183
+ "required": [
1184
+ "ok"
1185
+ ],
1186
+ "additionalProperties": false
1187
+ }
1188
+ }
1189
+ }
1190
+ },
1191
+ "401": {
1192
+ "description": "Missing or invalid bearer token"
1193
+ },
1194
+ "404": {
1195
+ "description": "No admin key with that id"
1196
+ },
1197
+ "405": {
1198
+ "description": "Method not allowed on this resource (see the `Allow` header)"
1199
+ },
1200
+ "409": {
1201
+ "description": "Conflict (already a member, last admin, slug taken, …)"
1202
+ }
1203
+ }
108
1204
  }
109
1205
  },
110
1206
  "/api/v1/users": {