@shipeasy/openapi 3.1.0 → 3.2.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.
@@ -601,3 +601,285 @@
601
601
  name: activity
602
602
  positional:
603
603
  - id
604
+ /api/admin/gates/{id}/whitelist:
605
+ get:
606
+ operationId: getGateWhitelist
607
+ summary: Read a gate's whitelist
608
+ description: |-
609
+ Returns the gate's whitelist — the always-first allowlist that admits the listed identities before any targeting rule or percentage rollout is evaluated.
610
+
611
+ A gate with no whitelist returns `entries: []` (and the default `attr`), never a 404 — so a caller can read-then-write without special-casing the empty gate.
612
+
613
+ **Use case:** Check whether an account is already let through before adding it.
614
+ tags:
615
+ - Flags
616
+ parameters:
617
+ - $ref: ../components/parameters.yaml#/ProjectId
618
+ - name: id
619
+ in: path
620
+ required: true
621
+ description: Stable opaque gate id (`gate_…`) or the gate's `name`.
622
+ schema:
623
+ $ref: ../components/schemas/common.yaml#/ResourceId
624
+ responses:
625
+ "200":
626
+ description: Read a gate's whitelist
627
+ content:
628
+ application/json:
629
+ schema:
630
+ $ref: ../components/schemas/gates.yaml#/GetGateWhitelistResponse
631
+ example:
632
+ id: gate_01j7w8a1b2c3d4e5f6g7h8i9j0
633
+ name: new_checkout
634
+ attr: email
635
+ entries:
636
+ - alice@acme.dev
637
+ - bob@acme.dev
638
+ "400":
639
+ $ref: ../components/responses.yaml#/BadRequest
640
+ "401":
641
+ $ref: ../components/responses.yaml#/Unauthorized
642
+ "403":
643
+ $ref: ../components/responses.yaml#/Forbidden
644
+ "404":
645
+ $ref: ../components/responses.yaml#/NotFound
646
+ "409":
647
+ $ref: ../components/responses.yaml#/Conflict
648
+ "422":
649
+ $ref: ../components/responses.yaml#/UnprocessableEntity
650
+ x-error-codes:
651
+ - BAD_REQUEST
652
+ - UNAUTHORIZED
653
+ - FORBIDDEN
654
+ - NOT_FOUND
655
+ x-cli:
656
+ name: whitelist
657
+ positional:
658
+ - id
659
+ put:
660
+ operationId: setGateWhitelist
661
+ summary: Replace a gate's whitelist
662
+ description: |-
663
+ Replaces the gate's whole whitelist with `entries`, creating the block if the gate didn't have one. Idempotent — the same call twice leaves the same list.
664
+
665
+ This is the only whitelist call that can switch `attr` (`email` ⇄ `user_id`) or clear the block: `entries: []` removes the whitelist from the gate entirely.
666
+
667
+ **Use cases**
668
+
669
+ - **Pin an exact list** — `{ "entries": ["alice@acme.dev", "bob@acme.dev"] }`.
670
+ - **Switch to user ids** — `{ "attr": "user_id", "entries": ["usr_123"] }`.
671
+ - **Drop the whitelist** — `{ "entries": [] }`.
672
+ tags:
673
+ - Flags
674
+ parameters:
675
+ - $ref: ../components/parameters.yaml#/ProjectId
676
+ - name: id
677
+ in: path
678
+ required: true
679
+ description: Stable opaque gate id (`gate_…`) or the gate's `name`.
680
+ schema:
681
+ $ref: ../components/schemas/common.yaml#/ResourceId
682
+ requestBody:
683
+ required: true
684
+ content:
685
+ application/json:
686
+ schema:
687
+ $ref: ../components/schemas/gates.yaml#/SetGateWhitelistRequest
688
+ examples:
689
+ pinList:
690
+ summary: Pin an exact list of emails
691
+ value:
692
+ entries:
693
+ - alice@acme.dev
694
+ - bob@acme.dev
695
+ switchToUserIds:
696
+ summary: Re-key the whitelist onto user ids
697
+ description: Switching `attr` replaces the entries wholesale — the old emails are not translated.
698
+ value:
699
+ attr: user_id
700
+ entries:
701
+ - usr_01j7w8a1b2c3d4e5f6g7h8i9j0
702
+ clear:
703
+ summary: Remove the whitelist
704
+ description: An empty list drops the pinned block from the gate's stack.
705
+ value:
706
+ entries: []
707
+ responses:
708
+ "200":
709
+ description: Replace a gate's whitelist
710
+ content:
711
+ application/json:
712
+ schema:
713
+ $ref: ../components/schemas/gates.yaml#/SetGateWhitelistResponse
714
+ example:
715
+ id: gate_01j7w8a1b2c3d4e5f6g7h8i9j0
716
+ name: new_checkout
717
+ attr: email
718
+ entries:
719
+ - alice@acme.dev
720
+ - bob@acme.dev
721
+ "400":
722
+ $ref: ../components/responses.yaml#/BadRequest
723
+ "401":
724
+ $ref: ../components/responses.yaml#/Unauthorized
725
+ "403":
726
+ $ref: ../components/responses.yaml#/Forbidden
727
+ "404":
728
+ $ref: ../components/responses.yaml#/NotFound
729
+ "409":
730
+ $ref: ../components/responses.yaml#/Conflict
731
+ "422":
732
+ $ref: ../components/responses.yaml#/UnprocessableEntity
733
+ x-error-codes:
734
+ - BAD_REQUEST
735
+ - UNAUTHORIZED
736
+ - FORBIDDEN
737
+ - NOT_FOUND
738
+ - VALIDATION
739
+ x-cli:
740
+ name: whitelist-set
741
+ positional:
742
+ - id
743
+ post:
744
+ operationId: addToGateWhitelist
745
+ summary: Add entries to a gate's whitelist
746
+ description: |-
747
+ Adds identities to the gate's whitelist, creating the block if the gate doesn't have one yet. Entries already on the list are skipped, so the call is idempotent and safe to retry.
748
+
749
+ Adding to a gate that already has a whitelist keyed on the other attribute is rejected (409) rather than silently re-keying the entries already there — use `PUT` to switch `attr` deliberately.
750
+
751
+ **Use case:** Let one more customer into a private beta without reading the current list first.
752
+ tags:
753
+ - Flags
754
+ parameters:
755
+ - $ref: ../components/parameters.yaml#/ProjectId
756
+ - name: id
757
+ in: path
758
+ required: true
759
+ description: Stable opaque gate id (`gate_…`) or the gate's `name`.
760
+ schema:
761
+ $ref: ../components/schemas/common.yaml#/ResourceId
762
+ requestBody:
763
+ required: true
764
+ content:
765
+ application/json:
766
+ schema:
767
+ $ref: ../components/schemas/gates.yaml#/AddToGateWhitelistRequest
768
+ examples:
769
+ addOne:
770
+ summary: Admit one more email
771
+ value:
772
+ entries:
773
+ - carol@acme.dev
774
+ createOnUserIds:
775
+ summary: Start a whitelist keyed on user ids
776
+ description: "`attr` is honoured only because this gate has no whitelist yet."
777
+ value:
778
+ attr: user_id
779
+ entries:
780
+ - usr_01j7w8a1b2c3d4e5f6g7h8i9j0
781
+ responses:
782
+ "200":
783
+ description: Add entries to a gate's whitelist
784
+ content:
785
+ application/json:
786
+ schema:
787
+ $ref: ../components/schemas/gates.yaml#/AddToGateWhitelistResponse
788
+ example:
789
+ id: gate_01j7w8a1b2c3d4e5f6g7h8i9j0
790
+ name: new_checkout
791
+ attr: email
792
+ entries:
793
+ - alice@acme.dev
794
+ - bob@acme.dev
795
+ - carol@acme.dev
796
+ "400":
797
+ $ref: ../components/responses.yaml#/BadRequest
798
+ "401":
799
+ $ref: ../components/responses.yaml#/Unauthorized
800
+ "403":
801
+ $ref: ../components/responses.yaml#/Forbidden
802
+ "404":
803
+ $ref: ../components/responses.yaml#/NotFound
804
+ "409":
805
+ $ref: ../components/responses.yaml#/Conflict
806
+ "422":
807
+ $ref: ../components/responses.yaml#/UnprocessableEntity
808
+ x-error-codes:
809
+ - BAD_REQUEST
810
+ - UNAUTHORIZED
811
+ - FORBIDDEN
812
+ - NOT_FOUND
813
+ - IMMUTABLE_FIELD
814
+ - VALIDATION
815
+ x-cli:
816
+ name: whitelist-add
817
+ positional:
818
+ - id
819
+ delete:
820
+ operationId: removeFromGateWhitelist
821
+ summary: Remove entries from a gate's whitelist
822
+ description: |-
823
+ Removes identities from the gate's whitelist. Entries that aren't on the list are skipped, so the call is idempotent.
824
+
825
+ Removing the last entry leaves an empty whitelist block in place; to drop the block itself use `PUT` with `entries: []`.
826
+
827
+ **Use case:** Revoke one beta tester's access without touching anyone else's.
828
+ tags:
829
+ - Flags
830
+ parameters:
831
+ - $ref: ../components/parameters.yaml#/ProjectId
832
+ - name: id
833
+ in: path
834
+ required: true
835
+ description: Stable opaque gate id (`gate_…`) or the gate's `name`.
836
+ schema:
837
+ $ref: ../components/schemas/common.yaml#/ResourceId
838
+ requestBody:
839
+ required: true
840
+ content:
841
+ application/json:
842
+ schema:
843
+ $ref: ../components/schemas/gates.yaml#/RemoveFromGateWhitelistRequest
844
+ examples:
845
+ removeOne:
846
+ summary: Revoke one email
847
+ value:
848
+ entries:
849
+ - carol@acme.dev
850
+ responses:
851
+ "200":
852
+ description: Remove entries from a gate's whitelist
853
+ content:
854
+ application/json:
855
+ schema:
856
+ $ref: ../components/schemas/gates.yaml#/RemoveFromGateWhitelistResponse
857
+ example:
858
+ id: gate_01j7w8a1b2c3d4e5f6g7h8i9j0
859
+ name: new_checkout
860
+ attr: email
861
+ entries:
862
+ - alice@acme.dev
863
+ - bob@acme.dev
864
+ "400":
865
+ $ref: ../components/responses.yaml#/BadRequest
866
+ "401":
867
+ $ref: ../components/responses.yaml#/Unauthorized
868
+ "403":
869
+ $ref: ../components/responses.yaml#/Forbidden
870
+ "404":
871
+ $ref: ../components/responses.yaml#/NotFound
872
+ "409":
873
+ $ref: ../components/responses.yaml#/Conflict
874
+ "422":
875
+ $ref: ../components/responses.yaml#/UnprocessableEntity
876
+ x-error-codes:
877
+ - BAD_REQUEST
878
+ - UNAUTHORIZED
879
+ - FORBIDDEN
880
+ - NOT_FOUND
881
+ - VALIDATION
882
+ x-cli:
883
+ name: whitelist-remove
884
+ positional:
885
+ - id
@@ -579,3 +579,99 @@
579
579
  name: set-value
580
580
  positional:
581
581
  - id
582
+ /api/admin/killswitches/{id}/toggle:
583
+ post:
584
+ operationId: toggleKillswitch
585
+ summary: Toggle a killswitch or one of its switches
586
+ description: |-
587
+ Flips a killswitch on one environment and publishes a new version there. This is the one-call incident verb: it reads the current value, flips it, and publishes, so you don't have to fetch the killswitch first.
588
+
589
+ Every body field is optional, which is what makes the call widen cleanly:
590
+
591
+ - **Flip the killswitch** — `{}`. Flips the flat `value` on `prod`.
592
+ - **Flip one sub-switch** — `{ "switchKey": "eu_region" }`. Flips that entry on `prod`, creating it (from `false`) if it isn't in the map yet.
593
+ - **Set it idempotently** — `{ "switchKey": "eu_region", "value": true }`. Publishes exactly that value, so a retried call can't undo the first one. A `null` `value` means "flip", not "set to null".
594
+ - **Choose the environment** — add `"env": "staging"`. Omitted, `env` is `prod`.
595
+
596
+ The response reports both `previous` and `value`, so a caller that asked for a flip can see what it actually changed.
597
+
598
+ Prefer this over `PUT /{id}/value` and `PUT /{id}/switch` unless you specifically need those endpoints' unconditional set semantics.
599
+ tags:
600
+ - Killswitch
601
+ parameters:
602
+ - $ref: ../components/parameters.yaml#/ProjectId
603
+ - name: id
604
+ in: path
605
+ required: true
606
+ description: Stable opaque killswitch id (`ksw_…`) or the killswitch's `name`.
607
+ schema:
608
+ $ref: ../components/schemas/common.yaml#/ResourceId
609
+ requestBody:
610
+ required: false
611
+ content:
612
+ application/json:
613
+ schema:
614
+ $ref: ../components/schemas/killswitches.yaml#/ToggleKillswitchRequest
615
+ examples:
616
+ flipOnProd:
617
+ summary: Flip the killswitch on prod
618
+ description: No body fields — reads the flat `value` on prod and publishes its opposite.
619
+ value: {}
620
+ flipOneSwitch:
621
+ summary: Flip one sub-switch on prod
622
+ description: Flips `switches.eu_region` on prod, leaving the flat `value` and every other key alone.
623
+ value:
624
+ switchKey: eu_region
625
+ setOneSwitch:
626
+ summary: Trip one sub-switch idempotently
627
+ description: Publishes `switches.eu_region = true` on prod whatever it was before — safe to retry.
628
+ value:
629
+ switchKey: eu_region
630
+ value: true
631
+ setOneSwitchOnStaging:
632
+ summary: Trip one sub-switch on a chosen env
633
+ description: The same idempotent set, published on staging instead of prod.
634
+ value:
635
+ switchKey: eu_region
636
+ value: true
637
+ env: staging
638
+ responses:
639
+ "200":
640
+ description: Toggle a killswitch or one of its switches
641
+ content:
642
+ application/json:
643
+ schema:
644
+ $ref: ../components/schemas/killswitches.yaml#/ToggleKillswitchResponse
645
+ example:
646
+ id: ksw_01j7w9d8h2k4m6n8p0q2r4s6t8
647
+ env: prod
648
+ switchKey: eu_region
649
+ previous: false
650
+ value: true
651
+ version: 7
652
+ published:
653
+ value: false
654
+ switches:
655
+ eu_region: true
656
+ "400":
657
+ $ref: ../components/responses.yaml#/BadRequest
658
+ "401":
659
+ $ref: ../components/responses.yaml#/Unauthorized
660
+ "403":
661
+ $ref: ../components/responses.yaml#/Forbidden
662
+ "404":
663
+ $ref: ../components/responses.yaml#/NotFound
664
+ "409":
665
+ $ref: ../components/responses.yaml#/Conflict
666
+ "422":
667
+ $ref: ../components/responses.yaml#/UnprocessableEntity
668
+ x-error-codes:
669
+ - BAD_REQUEST
670
+ - UNAUTHORIZED
671
+ - FORBIDDEN
672
+ - NOT_FOUND
673
+ - VALIDATION
674
+ x-cli:
675
+ name: toggle
676
+ positional:
677
+ - id
@@ -180,6 +180,185 @@
180
180
  summary: File a feature request.
181
181
  preset:
182
182
  type: feature_request
183
+ /ops/bug:
184
+ post:
185
+ operationId: createPublicBug
186
+ summary: File a bug
187
+ description: |-
188
+ Files one bug onto a project's queue, awaiting human approval. This is the **public** intake: it authenticates with a *client* SDK key rather than an admin key, so it can be called from a CLI, an installer script, a devtools overlay, or any shipped code — the same places a client key already lives.
189
+
190
+ Three gates decide whether a ticket is filed, and nothing else the caller sends can widen them:
191
+
192
+ 1. the key is a `client` key carrying the `tickets:public_create` scope,
193
+ 2. the key's project has public ticket creation enabled, and
194
+ 3. the item is filed as `pending_approval` — parked out of the work queue until a human promotes it in the dashboard.
195
+
196
+ The project is the key's own project; there is no `X-Project-Id` to pass and no way to file into someone else's queue. Repeat submissions of the same title dedupe against the open ticket already tracking it, which returns `200` with `deduped: true` instead of filing again.
197
+
198
+ This endpoint is served by the Shipeasy **edge worker** (`api.shipeasy.ai`), not the admin API — see `servers` below.
199
+
200
+ **Use case:** `shipeasy setup` fails on a customer's machine and self-reports the failure with the user's consent — `{ "title": "Setup failed at Feature installs", "stepsToReproduce": "…", "actualResult": "…" }`.
201
+ tags:
202
+ - Ops
203
+ servers:
204
+ - url: https://api.shipeasy.ai
205
+ description: Shipeasy edge worker (production)
206
+ - url: http://localhost:8787
207
+ description: Local `wrangler dev`
208
+ security:
209
+ - clientSdkKey: []
210
+ requestBody:
211
+ required: true
212
+ content:
213
+ application/json:
214
+ schema:
215
+ $ref: ../components/schemas/ops.yaml#/CreatePublicBugRequest
216
+ examples:
217
+ minimal:
218
+ summary: Just a title
219
+ description: Every other field is optional — the queue triages from `open` at default priority.
220
+ value:
221
+ title: Checkout button misaligned on mobile
222
+ full:
223
+ summary: A full report
224
+ value:
225
+ title: Checkout button misaligned on mobile
226
+ stepsToReproduce: Open the cart on iOS Safari and tap the price row.
227
+ actualResult: The primary CTA overlaps the price.
228
+ expectedResult: The CTA sits below the price with clear spacing.
229
+ priority: high
230
+ reporterEmail: alice@acme.dev
231
+ pageUrl: https://acme.dev/cart
232
+ responses:
233
+ "200":
234
+ description: An open ticket already tracks this report; nothing was filed
235
+ content:
236
+ application/json:
237
+ schema:
238
+ $ref: ../components/schemas/ops.yaml#/CreatePublicTicketResponse
239
+ example:
240
+ number: 7
241
+ deduped: true
242
+ "201":
243
+ description: File a bug
244
+ content:
245
+ application/json:
246
+ schema:
247
+ $ref: ../components/schemas/ops.yaml#/CreatePublicTicketResponse
248
+ example:
249
+ id: fb_01j7w8a1b2c3d4e5f6g7h8i9j0
250
+ number: 7
251
+ "400":
252
+ $ref: ../components/responses.yaml#/BadRequest
253
+ "401":
254
+ $ref: ../components/responses.yaml#/Unauthorized
255
+ "403":
256
+ $ref: ../components/responses.yaml#/Forbidden
257
+ "413":
258
+ $ref: ../components/responses.yaml#/PayloadTooLarge
259
+ "429":
260
+ $ref: ../components/responses.yaml#/TooManyRequests
261
+ "503":
262
+ $ref: ../components/responses.yaml#/ServiceUnavailable
263
+ x-error-codes:
264
+ - BAD_REQUEST
265
+ - UNAUTHORIZED
266
+ - FORBIDDEN
267
+ - VALIDATION
268
+ x-cli:
269
+ hidden: true
270
+ # Spec- and SDK-only. The generated CLI/MCP client resolves ONE base URL
271
+ # and ONE credential for the whole contract, so it cannot honour this
272
+ # operation's own `servers` (the edge worker) or its `clientSdkKey`
273
+ # security — it would POST to the admin host with an admin bearer and 404.
274
+ # Both surfaces are authenticated admin tools anyway: `shipeasy ops bug`
275
+ # and the `ops_bug` MCP tool file through `createOpsItem`, which lands an
276
+ # `open` item with the full field set rather than a `pending_approval` one.
277
+ /ops/feature-request:
278
+ post:
279
+ operationId: createPublicFeatureRequest
280
+ summary: File a feature request
281
+ description: |-
282
+ Files one feature request onto a project's queue, awaiting human approval. The feature-request counterpart to `POST /ops/bug`, with the same three gates: a `client` key carrying `tickets:public_create`, a project that has opted in, and a `pending_approval` state forced server-side.
283
+
284
+ The project is the key's own project; there is no `X-Project-Id` to pass. Repeat submissions of the same title dedupe against the open ticket already tracking it, which returns `200` with `deduped: true` instead of filing again.
285
+
286
+ This endpoint is served by the Shipeasy **edge worker** (`api.shipeasy.ai`), not the admin API — see `servers` below.
287
+
288
+ **Use case:** An in-product "request a feature" form posts what the user asked for — `{ "title": "Dark mode", "useCase": "Reduce eye strain at night" }`.
289
+ tags:
290
+ - Ops
291
+ servers:
292
+ - url: https://api.shipeasy.ai
293
+ description: Shipeasy edge worker (production)
294
+ - url: http://localhost:8787
295
+ description: Local `wrangler dev`
296
+ security:
297
+ - clientSdkKey: []
298
+ requestBody:
299
+ required: true
300
+ content:
301
+ application/json:
302
+ schema:
303
+ $ref: ../components/schemas/ops.yaml#/CreatePublicFeatureRequestRequest
304
+ examples:
305
+ minimal:
306
+ summary: Just a title
307
+ value:
308
+ title: Dark mode for the dashboard
309
+ full:
310
+ summary: A full request
311
+ value:
312
+ title: Dark mode for the dashboard
313
+ description: Add a theme toggle that persists per user.
314
+ useCase: Reduce eye strain for users working at night.
315
+ priority: nice_to_have
316
+ reporterEmail: alice@acme.dev
317
+ responses:
318
+ "200":
319
+ description: An open ticket already tracks this report; nothing was filed
320
+ content:
321
+ application/json:
322
+ schema:
323
+ $ref: ../components/schemas/ops.yaml#/CreatePublicTicketResponse
324
+ example:
325
+ number: 7
326
+ deduped: true
327
+ "201":
328
+ description: File a feature request
329
+ content:
330
+ application/json:
331
+ schema:
332
+ $ref: ../components/schemas/ops.yaml#/CreatePublicTicketResponse
333
+ example:
334
+ id: fb_01j7w8a1b2c3d4e5f6g7h8i9j0
335
+ number: 8
336
+ "400":
337
+ $ref: ../components/responses.yaml#/BadRequest
338
+ "401":
339
+ $ref: ../components/responses.yaml#/Unauthorized
340
+ "403":
341
+ $ref: ../components/responses.yaml#/Forbidden
342
+ "413":
343
+ $ref: ../components/responses.yaml#/PayloadTooLarge
344
+ "429":
345
+ $ref: ../components/responses.yaml#/TooManyRequests
346
+ "503":
347
+ $ref: ../components/responses.yaml#/ServiceUnavailable
348
+ x-error-codes:
349
+ - BAD_REQUEST
350
+ - UNAUTHORIZED
351
+ - FORBIDDEN
352
+ - VALIDATION
353
+ x-cli:
354
+ hidden: true
355
+ # Spec- and SDK-only. The generated CLI/MCP client resolves ONE base URL
356
+ # and ONE credential for the whole contract, so it cannot honour this
357
+ # operation's own `servers` (the edge worker) or its `clientSdkKey`
358
+ # security — it would POST to the admin host with an admin bearer and 404.
359
+ # Both surfaces are authenticated admin tools anyway: `shipeasy ops bug`
360
+ # and the `ops_bug` MCP tool file through `createOpsItem`, which lands an
361
+ # `open` item with the full field set rather than a `pending_approval` one.
183
362
  /api/admin/ops/{handle}:
184
363
  get:
185
364
  operationId: getOpsItem