@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.
- package/dist/{chunk-UV333BIK.js → chunk-DW33MG4B.js} +147 -2
- package/dist/chunk-DW33MG4B.js.map +1 -0
- package/dist/{chunk-V4ZESAIF.js → chunk-RTN572TI.js} +67 -1
- package/dist/chunk-RTN572TI.js.map +1 -0
- package/dist/{client-yqGYju-M.d.ts → client-BPVn4hMC.d.ts} +687 -3
- package/dist/client.d.ts +1 -1
- package/dist/client.js +15 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +376 -1
- package/dist/schemas.js +67 -1
- package/openapi-sdk.json +1747 -0
- package/openapi.json +1485 -178
- package/openapi.yaml +922 -2
- package/package.json +6 -4
- package/spec/components/responses.yaml +39 -0
- package/spec/components/schemas/gates.yaml +87 -0
- package/spec/components/schemas/keys.yaml +2 -2
- package/spec/components/schemas/killswitches.yaml +64 -0
- package/spec/components/schemas/ops.yaml +185 -0
- package/spec/openapi-sdk.yaml +86 -0
- package/spec/openapi.yaml +18 -0
- package/spec/paths/gates.yaml +282 -0
- package/spec/paths/killswitches.yaml +96 -0
- package/spec/paths/ops.yaml +179 -0
- package/dist/chunk-UV333BIK.js.map +0 -1
- package/dist/chunk-V4ZESAIF.js.map +0 -1
package/spec/paths/gates.yaml
CHANGED
|
@@ -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
|
package/spec/paths/ops.yaml
CHANGED
|
@@ -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
|