@sponson/adapters 0.0.0-stage → 0.5.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.
Files changed (132) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +4 -2
  3. package/dist/algolia.d.ts +6 -0
  4. package/dist/algolia.js +151 -0
  5. package/dist/algolia.js.map +1 -0
  6. package/dist/clerk.d.ts +8 -0
  7. package/dist/clerk.js +107 -0
  8. package/dist/clerk.js.map +1 -0
  9. package/dist/cloudflare.d.ts +25 -0
  10. package/dist/cloudflare.js +310 -0
  11. package/dist/cloudflare.js.map +1 -0
  12. package/dist/common.d.ts +73 -0
  13. package/dist/common.js +140 -0
  14. package/dist/common.js.map +1 -0
  15. package/dist/digest.d.ts +46 -0
  16. package/dist/digest.js +129 -0
  17. package/dist/digest.js.map +1 -0
  18. package/dist/digitalocean.d.ts +5 -0
  19. package/dist/digitalocean.js +243 -0
  20. package/dist/digitalocean.js.map +1 -0
  21. package/dist/http-adapter-action.d.ts +63 -0
  22. package/dist/http-adapter-action.js +369 -0
  23. package/dist/http-adapter-action.js.map +1 -0
  24. package/dist/http-adapter-auth.d.ts +88 -0
  25. package/dist/http-adapter-auth.js +437 -0
  26. package/dist/http-adapter-auth.js.map +1 -0
  27. package/dist/http-adapter-client.d.ts +51 -0
  28. package/dist/http-adapter-client.js +156 -0
  29. package/dist/http-adapter-client.js.map +1 -0
  30. package/dist/http-adapter-graphql.d.ts +39 -0
  31. package/dist/http-adapter-graphql.js +157 -0
  32. package/dist/http-adapter-graphql.js.map +1 -0
  33. package/dist/http-adapter-list.d.ts +33 -0
  34. package/dist/http-adapter-list.js +421 -0
  35. package/dist/http-adapter-list.js.map +1 -0
  36. package/dist/http-adapter-resource.d.ts +29 -0
  37. package/dist/http-adapter-resource.js +462 -0
  38. package/dist/http-adapter-resource.js.map +1 -0
  39. package/dist/http-adapter-spec.d.ts +216 -0
  40. package/dist/http-adapter-spec.js +660 -0
  41. package/dist/http-adapter-spec.js.map +1 -0
  42. package/dist/http-adapter.d.ts +16 -0
  43. package/dist/http-adapter.js +28 -0
  44. package/dist/http-adapter.js.map +1 -0
  45. package/dist/http.d.ts +179 -0
  46. package/dist/http.js +352 -0
  47. package/dist/http.js.map +1 -0
  48. package/dist/index.d.ts +28 -0
  49. package/dist/index.js +45 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/launchdarkly.d.ts +15 -0
  52. package/dist/launchdarkly.js +337 -0
  53. package/dist/launchdarkly.js.map +1 -0
  54. package/dist/manual.d.ts +13 -0
  55. package/dist/manual.js +201 -0
  56. package/dist/manual.js.map +1 -0
  57. package/dist/neon.d.ts +8 -0
  58. package/dist/neon.js +167 -0
  59. package/dist/neon.js.map +1 -0
  60. package/dist/netlify.d.ts +18 -0
  61. package/dist/netlify.js +436 -0
  62. package/dist/netlify.js.map +1 -0
  63. package/dist/planetscale.d.ts +10 -0
  64. package/dist/planetscale.js +311 -0
  65. package/dist/planetscale.js.map +1 -0
  66. package/dist/providers.d.ts +26 -0
  67. package/dist/providers.js +36 -0
  68. package/dist/providers.js.map +1 -0
  69. package/dist/recipes.d.ts +167 -0
  70. package/dist/recipes.js +246 -0
  71. package/dist/recipes.js.map +1 -0
  72. package/dist/secrets.d.ts +72 -0
  73. package/dist/secrets.js +476 -0
  74. package/dist/secrets.js.map +1 -0
  75. package/dist/sigv4.d.ts +56 -0
  76. package/dist/sigv4.js +118 -0
  77. package/dist/sigv4.js.map +1 -0
  78. package/dist/supabase.d.ts +9 -0
  79. package/dist/supabase.js +385 -0
  80. package/dist/supabase.js.map +1 -0
  81. package/dist/vercel.d.ts +8 -0
  82. package/dist/vercel.js +475 -0
  83. package/dist/vercel.js.map +1 -0
  84. package/dist/xata.d.ts +8 -0
  85. package/dist/xata.js +142 -0
  86. package/dist/xata.js.map +1 -0
  87. package/dist/xml.d.ts +25 -0
  88. package/dist/xml.js +211 -0
  89. package/dist/xml.js.map +1 -0
  90. package/package.json +58 -3
  91. package/recipes/amplify.yaml +147 -0
  92. package/recipes/auth0.yaml +178 -0
  93. package/recipes/azure_swa.yaml +116 -0
  94. package/recipes/cloud_run.yaml +97 -0
  95. package/recipes/cloudflare.yaml +108 -0
  96. package/recipes/cockroachdb.yaml +132 -0
  97. package/recipes/configcat.yaml +123 -0
  98. package/recipes/datadog.yaml +136 -0
  99. package/recipes/firebase.yaml +84 -0
  100. package/recipes/flagsmith.yaml +98 -0
  101. package/recipes/fly.yaml +97 -0
  102. package/recipes/fly_machines.yaml +67 -0
  103. package/recipes/gcs.yaml +102 -0
  104. package/recipes/github.yaml +123 -0
  105. package/recipes/growthbook.yaml +124 -0
  106. package/recipes/heroku.yaml +98 -0
  107. package/recipes/honeycomb.yaml +89 -0
  108. package/recipes/inngest.yaml +97 -0
  109. package/recipes/kinde.yaml +120 -0
  110. package/recipes/mongodb_atlas.yaml +119 -0
  111. package/recipes/okta.yaml +177 -0
  112. package/recipes/posthog.yaml +127 -0
  113. package/recipes/postmark.yaml +105 -0
  114. package/recipes/postmark_server.yaml +93 -0
  115. package/recipes/prisma.yaml +150 -0
  116. package/recipes/qstash.yaml +134 -0
  117. package/recipes/railway.yaml +130 -0
  118. package/recipes/render.yaml +103 -0
  119. package/recipes/resend.yaml +130 -0
  120. package/recipes/s3.yaml +108 -0
  121. package/recipes/sendgrid.yaml +165 -0
  122. package/recipes/sentry.yaml +158 -0
  123. package/recipes/split.yaml +121 -0
  124. package/recipes/statsig.yaml +124 -0
  125. package/recipes/stripe.yaml +310 -0
  126. package/recipes/stytch.yaml +113 -0
  127. package/recipes/svix.yaml +109 -0
  128. package/recipes/turso.yaml +98 -0
  129. package/recipes/unleash.yaml +149 -0
  130. package/recipes/upstash.yaml +130 -0
  131. package/recipes/vercel.yaml +116 -0
  132. package/recipes/workos.yaml +70 -0
@@ -0,0 +1,121 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Split (Harness FME) Admin API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: split
4
+ title: Split (Harness FME)
5
+ category: flags
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: Authentication
9
+ url: https://docs.split.io/reference/authentication
10
+ - title: Get feature flag definition in environment
11
+ url: https://docs.split.io/reference/get-feature-flag-definition-in-environment
12
+ - title: Full update feature flag definition in environment
13
+ url: https://docs.split.io/reference/full-update-feature-flag-definition-in-environment
14
+ - title: Feature flag definition, rule, condition, matcher
15
+ url: https://docs.split.io/reference/feature-flag-definition
16
+ - title: Matcher types
17
+ url: https://docs.split.io/reference/matcher-type
18
+ - title: API for Split admins after the move to Harness
19
+ url: https://developer.harness.io/docs/feature-management-experimentation/split-to-harness/api-for-split-admins/
20
+ api:
21
+ base_url: https://api.split.io/internal/api/v2
22
+ base_url_env: SPLIT_API_URL
23
+ auth: { bearer_env: SPLIT_ADMIN_API_KEY }
24
+ detect:
25
+ deps: ["@splitsoftware/"]
26
+ env: ["SPLIT_*"]
27
+ starter:
28
+ - op: targeting_rule
29
+ values: { match: "${ctx.scope}" }
30
+ where:
31
+ workspace_id: "Split → Admin settings → Projects: the project's id"
32
+ assumptions:
33
+ - id: SP1
34
+ verified: true
35
+ text: "The Admin API is at `https://api.split.io/internal/api/v2`, authenticated as `Authorization: Bearer` with a Split Admin API key (or a Harness PAT/SAT, accepted as Bearer on the non-deprecated endpoints). Host and paths are unchanged by the move to Harness; the workspace id is still Split's `wsId`."
36
+ source: https://developer.harness.io/docs/feature-management-experimentation/split-to-harness/api-for-split-admins/
37
+ - id: SP2
38
+ verified: true
39
+ text: "`GET /splits/ws/{wsId}/{featureFlagName}/environments/{environmentIdOrName}` answers the flag's definition in the environment bare: `treatments`, `defaultTreatment`, `baselineTreatment`, `trafficAllocation`, `rules`, `defaultRule` and read-only fields (`name`, `environment`, `trafficType`, times)."
40
+ source: https://docs.split.io/reference/get-feature-flag-definition-in-environment
41
+ - id: SP3
42
+ verified: true
43
+ text: "`PUT` on the same path replaces the definition with `treatments`, `defaultTreatment`, `baselineTreatment`, `trafficAllocation`, `rules` and `defaultRule` (the first three of the required fields). Those are sent as read, with the rule list changed (`parent.send`); whether PUT takes the read-only fields back is not documented, so they are not sent."
44
+ source: https://docs.split.io/reference/full-update-feature-flag-definition-in-environment
45
+ - id: SP4
46
+ verified: true
47
+ text: "A rule is `{ condition: { combiner: AND, matchers: [...] }, buckets: [{ treatment, size }] }` (bucket sizes sum to 100); rules have no id and are evaluated in order. `IN_LIST_STRING` with `attribute` and `strings` matches an attribute in a list."
48
+ source: https://docs.split.io/reference/matcher-type
49
+ - id: SP5
50
+ verified: false
51
+ text: "A rule is read back with its matcher exactly as written (no added `negate: false` or null members); otherwise every plan shows the rule as changed."
52
+ - id: SP6
53
+ verified: false
54
+ text: "A full update leaves the flag's killed state alone (`killed` is not a documented PUT field)."
55
+ - id: SP7
56
+ verified: true
57
+ text: "In a project that requires a title and comment on changes, they are query parameters of the update; this recipe does not send them, so such a project refuses the write and the apply fails."
58
+ source: https://developer.harness.io/docs/feature-management-experimentation/api/feature-flags/
59
+ ops:
60
+ targeting_rule:
61
+ kind: list_item
62
+ title: Feature flag targeting rule
63
+ summary: "A targeting rule on a feature flag's definition in one environment that serves a treatment to the preview, such as `if preview is in list [pr-42] serve on`. Added after the flag's other rules (evaluated in order: an earlier matching rule wins); the treatments, default rule and traffic allocation are written back as read. Identified by the matched value; a console edit of its matcher or treatment is drift. Destroy removes the rule."
64
+ covers: [split-rule]
65
+ params:
66
+ workspace_id:
67
+ type: string
68
+ required: true
69
+ description: The workspace (project) id, Split's `wsId`.
70
+ example: a3b6f5d0-0e8f-11ee-9b0f-0242ac120002
71
+ flag:
72
+ type: string
73
+ required: true
74
+ description: The feature flag's name.
75
+ example: new_checkout
76
+ environment:
77
+ type: string
78
+ required: true
79
+ description: The environment's id or name; the flag must have a definition there.
80
+ example: Staging
81
+ attribute:
82
+ type: string
83
+ required: true
84
+ description: The attribute the rule tests (passed with the evaluation).
85
+ example: preview
86
+ match:
87
+ type: string
88
+ required: true
89
+ description: The attribute value that is the preview; it identifies the rule.
90
+ example: "${ctx.scope}"
91
+ treatment:
92
+ type: string
93
+ required: true
94
+ description: The treatment served to the preview; one of the flag's treatments.
95
+ example: "on"
96
+ http:
97
+ parent:
98
+ path: "/splits/ws/{workspace_id}/{flag}/environments/{environment}"
99
+ method: PUT
100
+ send: [treatments, defaultTreatment, baselineTreatment, trafficAllocation, defaultRule]
101
+ list_path: /rules
102
+ key_field: /condition/matchers/0/strings/0
103
+ item:
104
+ condition:
105
+ combiner: AND
106
+ matchers:
107
+ - type: IN_LIST_STRING
108
+ attribute: { param: attribute }
109
+ strings: [{ param: match }]
110
+ buckets: [{ treatment: { param: treatment }, size: 100 }]
111
+ sim:
112
+ objects:
113
+ "/splits/ws/{workspace_id}/{flag}/environments/{environment}":
114
+ treatments: [{ name: "on" }, { name: "off" }]
115
+ defaultTreatment: "off"
116
+ baselineTreatment: "off"
117
+ trafficAllocation: 100
118
+ rules: []
119
+ defaultRule: [{ treatment: "off", size: 100 }]
120
+ live:
121
+ params: { workspace_id: SPONSON_LIVE_SPLIT_WORKSPACE_ID, flag: SPONSON_LIVE_SPLIT_FLAG, environment: SPONSON_LIVE_SPLIT_ENVIRONMENT }
@@ -0,0 +1,124 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Statsig Console API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: statsig
4
+ title: Statsig
5
+ category: flags
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: Console API introduction (base URL, STATSIG-API-KEY, STATSIG-API-VERSION)
9
+ url: https://docs.statsig.com/console-api/introduction
10
+ - title: Add gate rule
11
+ url: https://docs.statsig.com/api-reference/gates/add-gate-rule
12
+ - title: Read gate rules
13
+ url: https://docs.statsig.com/api-reference/gates/read-gate-rules
14
+ - title: Update gate rules (one rule)
15
+ url: https://docs.statsig.com/api-reference/gates/update-gate-rules
16
+ - title: Delete gate rule
17
+ url: https://docs.statsig.com/api-reference/gates/delete-gate-rule
18
+ - title: Console API OpenAPI spec (20240601)
19
+ url: https://api.statsig.com/openapi/20240601.json
20
+ api:
21
+ base_url: https://statsigapi.net/console/v1
22
+ base_url_env: STATSIG_API_URL
23
+ auth: { header: STATSIG-API-KEY, value_env: STATSIG_CONSOLE_API_KEY }
24
+ headers: { STATSIG-API-VERSION: "20240601" }
25
+ detect:
26
+ deps: [statsig-js, statsig-node, statsig-react, "@statsig/"]
27
+ env: ["STATSIG_*"]
28
+ starter:
29
+ - op: gate_rule
30
+ values: { name: "preview ${ctx.scope}", values: ["${ctx.scope}"] }
31
+ where:
32
+ gate_id: "Statsig console → Feature Gates → your gate: its name in the URL"
33
+ assumptions:
34
+ - id: SG1
35
+ verified: true
36
+ text: "The Console API is at `https://statsigapi.net/console/v1`, authenticated with a Console API key in the `STATSIG-API-KEY` header; `STATSIG-API-VERSION: 20240601` (the only version) is optional today and announced as required later, so it is always sent."
37
+ source: https://docs.statsig.com/console-api/introduction
38
+ - id: SG2
39
+ verified: true
40
+ text: "`POST /gates/{id}/rule` (singular) adds one rule (`name`, `passPercentage`, `conditions`, `environments`) and answers `{ message, data: <the whole gate> }`, not the rule: the new rule is located by name afterwards (`create.locate`)."
41
+ source: https://docs.statsig.com/api-reference/gates/add-gate-rule
42
+ - id: SG3
43
+ verified: false
44
+ text: "`GET /gates/{id}/rules` answers the rules under `data.rules` (the documented example); the spec's schema says `data` is a list of `{ rules }` objects instead. If the API follows the schema, every read fails with PROVIDER_RESPONSE naming `find.list_path` (it does not create duplicates). The rules of one gate fit on the first page."
45
+ source: https://docs.statsig.com/api-reference/gates/read-gate-rules
46
+ - id: SG4
47
+ verified: true
48
+ text: "`PATCH /gates/{id}/rules/{ruleID}` updates the given fields of one rule; `DELETE` on the same path removes it. Rules carry a server-assigned `id`."
49
+ source: https://docs.statsig.com/api-reference/gates/update-gate-rules
50
+ - id: SG5
51
+ verified: false
52
+ text: "Rule names are unique within a gate (not documented): the rule is identified by its name, and two rules with that name are PARAM_INVALID rather than a guess."
53
+ - id: SG6
54
+ verified: false
55
+ text: "A rule is read back with `conditions` exactly as written (`type`, `operator`, `targetValue`, and `field` when sent). If Statsig adds keys such as `field: null` to a condition, every plan shows the rule as changed."
56
+ - id: SG7
57
+ verified: false
58
+ text: "DELETE of a rule that is already gone answers 404 (the spec lists only 200); any other answer fails the destroy rather than passing silently."
59
+ - id: SG8
60
+ verified: false
61
+ text: "On a gate that requires reviews, what a Console API rule write does (apply, fail, or open a pending review) is not documented; a write that does not take effect is seen on the next read as missing or changed, never as done."
62
+ source: https://docs.statsig.com/guides/setting-up-reviews
63
+ ops:
64
+ gate_rule:
65
+ kind: resource
66
+ title: Feature gate rule
67
+ summary: "A rule on an existing feature gate that passes the preview, such as `custom_field preview any [pr-42]`. Located by its name in the gate's rules; a console edit of its pass percentage, conditions or environments is drift. Destroy deletes the rule."
68
+ covers: [statsig-gate-rule]
69
+ params:
70
+ gate_id:
71
+ type: string
72
+ required: true
73
+ description: The gate's id (its name in the console URL).
74
+ example: new_checkout
75
+ name:
76
+ type: string
77
+ required: true
78
+ description: The rule's name, unique within the gate; it identifies the rule.
79
+ example: "preview ${ctx.scope}"
80
+ condition_type:
81
+ type: string
82
+ default: custom_field
83
+ enum: [custom_field, url, environment_tier, user_id, email]
84
+ description: "The condition's type: `custom_field` (with `field`), `url`, `environment_tier`, `user_id` or `email`."
85
+ field:
86
+ type: string
87
+ description: The custom field's name, for `condition_type` `custom_field`.
88
+ example: preview
89
+ operator:
90
+ type: string
91
+ default: any
92
+ description: "The condition's operator: `any`, `none`, `str_contains_any`, … (see the Statsig rule docs)."
93
+ values:
94
+ type: list
95
+ required: true
96
+ description: The values the condition matches (its `targetValue`).
97
+ example: ["${ctx.scope}"]
98
+ pass_percentage:
99
+ type: number
100
+ default: 100
101
+ description: The share of matching users that pass (0–100).
102
+ environment_names:
103
+ type: list
104
+ description: The environments the rule applies in (for example `[development, staging]`); all when left out.
105
+ http:
106
+ find:
107
+ path: "/gates/{gate_id}/rules"
108
+ list_path: /data/rules
109
+ match: { name: { param: name } }
110
+ create: { path: "/gates/{gate_id}/rule", locate: true }
111
+ update: { method: PATCH, path: "/gates/{gate_id}/rules/{id}" }
112
+ delete: { path: "/gates/{gate_id}/rules/{id}" }
113
+ fields:
114
+ passPercentage: { param: pass_percentage }
115
+ conditions:
116
+ - type: { param: condition_type }
117
+ field: { param: field }
118
+ operator: { param: operator }
119
+ targetValue: { param: values }
120
+ environments: { param: environment_names }
121
+ sim:
122
+ collection: "/gates/{gate_id}/rules"
123
+ live:
124
+ params: { gate_id: SPONSON_LIVE_STATSIG_GATE_ID }
@@ -0,0 +1,310 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Stripe API v1 recipes: a test-mode webhook endpoint per preview, and the catalog objects (products, prices,
3
+ # coupons) a preview's code expects. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
4
+ provider: stripe
5
+ title: Stripe
6
+ category: webhooks
7
+ verified_on: "2026-10-11"
8
+ docs:
9
+ - title: Authentication
10
+ url: https://docs.stripe.com/api/authentication
11
+ - title: API keys
12
+ url: https://docs.stripe.com/keys
13
+ - title: Idempotent requests
14
+ url: https://docs.stripe.com/api/idempotent_requests
15
+ - title: Create a webhook endpoint
16
+ url: https://docs.stripe.com/api/webhook_endpoints/create
17
+ - title: List all webhook endpoints
18
+ url: https://docs.stripe.com/api/webhook_endpoints/list
19
+ - title: Update a webhook endpoint
20
+ url: https://docs.stripe.com/api/webhook_endpoints/update
21
+ - title: Delete a webhook endpoint
22
+ url: https://docs.stripe.com/api/webhook_endpoints/delete
23
+ - title: Webhooks in the Dashboard (endpoint limit)
24
+ url: https://docs.stripe.com/development/dashboard/webhooks
25
+ - title: Create a product
26
+ url: https://docs.stripe.com/api/products/create
27
+ - title: Retrieve a product
28
+ url: https://docs.stripe.com/api/products/retrieve
29
+ - title: Delete a product
30
+ url: https://docs.stripe.com/api/products/delete
31
+ - title: Create a price
32
+ url: https://docs.stripe.com/api/prices/create
33
+ - title: List all prices
34
+ url: https://docs.stripe.com/api/prices/list
35
+ - title: Update a price
36
+ url: https://docs.stripe.com/api/prices/update
37
+ - title: Create a coupon
38
+ url: https://docs.stripe.com/api/coupons/create
39
+ api:
40
+ base_url: https://api.stripe.com/v1
41
+ base_url_env: STRIPE_API_URL
42
+ auth: { bearer_env: STRIPE_SECRET_KEY }
43
+ encoding: form
44
+ # A preview must never create live-mode objects (ST2): live keys are refused unless this variable says otherwise.
45
+ refuse_credential: { prefixes: [sk_live_, rk_live_], unless_env: SPONSON_STRIPE_ALLOW_LIVE }
46
+ detect:
47
+ deps: [stripe, "@stripe/"]
48
+ env: ["STRIPE_*"]
49
+ starter:
50
+ - op: webhook_endpoint
51
+ preview_url: url
52
+ values: { name: "preview-${ctx.scope}" }
53
+ assumptions:
54
+ - id: ST1
55
+ verified: true
56
+ text: "The API takes a secret or restricted key as `Authorization: Bearer` (besides Basic), and request bodies form-encoded with bracketed nesting (`metadata[k]=v`, `enabled_events[0]=x`)."
57
+ source: https://docs.stripe.com/api/authentication
58
+ - id: ST2
59
+ verified: true
60
+ text: "Test-mode keys start with `sk_test_` / `rk_test_`, live-mode keys with `sk_live_` / `rk_live_`; objects of one mode are invisible to the other. The recipe refuses the live prefixes (`refuse_credential`) unless `SPONSON_STRIPE_ALLOW_LIVE=1`."
61
+ source: https://docs.stripe.com/keys
62
+ - id: ST3
63
+ verified: false
64
+ text: "Organization keys (`sk_org_…`) and platform-managed keys do not carry the mode in their prefix, so they are not refused: use a test-mode secret or restricted key for previews."
65
+ - id: ST4
66
+ verified: true
67
+ text: "Every v1 `POST` accepts an `Idempotency-Key` (up to 255 characters, kept at least 24 hours); a retry with the same key returns the saved answer, and different parameters under one key are an error."
68
+ source: https://docs.stripe.com/api/idempotent_requests
69
+ - id: ST5
70
+ verified: true
71
+ text: "`POST /v1/webhook_endpoints` takes `url`, `enabled_events` (`['*']` for all), `description`, `metadata` and `api_version`, and answers the endpoint with its signing `secret` populated."
72
+ source: https://docs.stripe.com/api/webhook_endpoints/create
73
+ - id: ST6
74
+ verified: true
75
+ text: "`GET /v1/webhook_endpoints` (`limit` 1–100) answers `{ data: [...], has_more }` whose endpoints carry `url`, `enabled_events`, `description` and `metadata` but no `secret`: the secret exists only in the create's answer, hence the `once` output."
76
+ source: https://docs.stripe.com/api/webhook_endpoints/list
77
+ - id: ST7
78
+ verified: true
79
+ text: "An account has at most 16 webhook endpoints, so one list page of 100 holds them all, and at most 16 previews (minus the account's own endpoints) can have one at a time."
80
+ source: https://docs.stripe.com/development/dashboard/webhooks
81
+ - id: ST8
82
+ verified: true
83
+ text: "`POST /v1/webhook_endpoints/{id}` updates `url`, `enabled_events`, `description`, `metadata` and `disabled` in place (the secret is not rotated); `DELETE` on the same path removes the endpoint."
84
+ source: https://docs.stripe.com/api/webhook_endpoints/update
85
+ - id: ST9
86
+ verified: false
87
+ text: "The API returns `url`, `enabled_events` and `description` as they were written, `enabled_events` in the order sent, so an unchanged endpoint hashes the same on every plan."
88
+ - id: ST10
89
+ verified: true
90
+ text: "`POST /v1/products` takes a client-chosen `id` (unique among the account's products), `name`, `description` and `active`; `GET /v1/products/{id}` retrieves it; `POST /v1/products/{id}` updates it."
91
+ source: https://docs.stripe.com/api/products/create
92
+ - id: ST11
93
+ verified: true
94
+ text: "A product can be deleted only while it has no prices, and prices are never deleted, so the recipe archives a product (`active: false`) instead of deleting it."
95
+ source: https://docs.stripe.com/api/products/delete
96
+ - id: ST12
97
+ verified: false
98
+ text: "An archived product is still returned by `GET /v1/products/{id}`, and its id stays taken: re-applying a destroyed scope finds it and reactivates it as an adopted object, which a later destroy leaves active."
99
+ - id: ST13
100
+ verified: true
101
+ text: "`POST /v1/prices` takes `product`, `currency`, `unit_amount`, `recurring[interval]`, `nickname`, `lookup_key` and `transfer_lookup_key` (which moves the lookup key from the price holding it to the new one)."
102
+ source: https://docs.stripe.com/api/prices/create
103
+ - id: ST14
104
+ verified: true
105
+ text: "`GET /v1/prices` lists active prices by default, filters by up to 10 `lookup_keys` and by `active`, and answers `{ data: [...] }`."
106
+ source: https://docs.stripe.com/api/prices/list
107
+ - id: ST15
108
+ verified: true
109
+ text: "A price's amount, currency and interval cannot change after creation; `POST /v1/prices/{id}` updates `active`, `nickname`, `lookup_key` and `metadata`. There is no delete: the recipe archives (`active: false`)."
110
+ source: https://docs.stripe.com/api/prices/update
111
+ - id: ST16
112
+ verified: true
113
+ text: "`POST /v1/coupons` takes a client-chosen `id`, `name`, `percent_off` or `amount_off` with `currency`, `duration` (`once`, `repeating`, `forever`) and `duration_in_months`."
114
+ source: https://docs.stripe.com/api/coupons/create
115
+ - id: ST17
116
+ verified: false
117
+ text: "`GET /v1/coupons/{id}` retrieves a coupon, `POST /v1/coupons/{id}` updates its `name` and `metadata` only (the discount is fixed), and `DELETE /v1/coupons/{id}` deletes it, after which its id may be used again."
118
+ source: https://docs.stripe.com/api/coupons/create
119
+ - id: ST18
120
+ verified: false
121
+ text: "Stripe must reach the endpoint's URL over public HTTPS: a preview behind deployment protection (Vercel Authentication) receives no events unless it lets Stripe's requests through."
122
+ ops:
123
+ webhook_endpoint:
124
+ kind: resource
125
+ title: Webhook endpoint
126
+ summary: "A test-mode webhook endpoint that sends the preview's events to its own URL, `url` + `path` (usually `{ from: web.preview_url }`). Identified by `name` in its metadata, so a new preview URL updates the endpoint in place. Its signing `secret` exists only in the answer that creates the endpoint: it is a once-only output, passed in the same run to the line that writes it (`STRIPE_WEBHOOK_SECRET` in a `vercel.env` or `netlify.env` line); later runs keep what that line holds, and `apply --recreate <line>` rotates it. Writing the secret rebuilds the preview, and a preview URL output follows the newest deploy, so the next run moves the endpoint to the rebuilt deploy's URL (in place: the secret is kept); a URL that does not change per deploy (`https://deploy-preview-${ctx.pr.number}--<site>.netlify.app`, a custom preview domain) avoids that update. A console edit of its URL, events or description is drift."
127
+ covers: [stripe-webhook]
128
+ params:
129
+ name:
130
+ type: string
131
+ required: true
132
+ pattern: "^[A-Za-z0-9_.:/-]{1,200}$"
133
+ description: "The endpoint's identity, kept in its metadata (`sponson_endpoint`): unique per preview, such as `preview-${ctx.scope}`."
134
+ example: "preview-${ctx.scope}"
135
+ url:
136
+ type: string
137
+ required: true
138
+ pattern: "^https://\\S*[^/]$"
139
+ description: "The preview's base URL, without a trailing slash: usually `{ from: <vercel.env or netlify.env line>.preview_url }`."
140
+ example: https://pr-42.preview.example.com
141
+ path:
142
+ type: string
143
+ default: /api/webhooks/stripe
144
+ pattern: "^/\\S*$"
145
+ description: The path of the app's webhook handler, appended to `url`.
146
+ enabled_events:
147
+ type: list
148
+ default: ["*"]
149
+ description: "The event types sent to the endpoint (`['*']`: all that need no explicit selection)."
150
+ example: [checkout.session.completed, customer.subscription.updated]
151
+ description:
152
+ type: string
153
+ description: A note shown in the Dashboard.
154
+ example: Preview webhook managed by Sponson
155
+ api_version:
156
+ type: string
157
+ description: The Stripe API version events are rendered with (the account's default when left out); sent on create only.
158
+ http:
159
+ find:
160
+ path: /webhook_endpoints?limit=100
161
+ list_path: /data
162
+ match: { /metadata/sponson_endpoint: { param: name } }
163
+ create:
164
+ path: /webhook_endpoints
165
+ idempotency_key: true
166
+ body: { api_version: { param: api_version } }
167
+ update: { method: POST, path: "/webhook_endpoints/{id}" }
168
+ delete: { path: "/webhook_endpoints/{id}" }
169
+ fields:
170
+ url: { join: [{ param: url }, { param: path }] }
171
+ enabled_events: { param: enabled_events }
172
+ description: { param: description }
173
+ outputs:
174
+ secret: { path: /secret, sensitive: true, once: true }
175
+ live:
176
+ params: { url: SPONSON_LIVE_STRIPE_WEBHOOK_URL }
177
+ product:
178
+ kind: resource
179
+ title: Product
180
+ summary: "A test-mode product with an id of the plan's choosing, such as `pro-pr-42`, for the prices a preview sells. Destroy archives it (`active: false`): Stripe never deletes a product that has prices. A console edit of its name, description or active flag is drift."
181
+ covers: [stripe-test-objects]
182
+ params:
183
+ product_id:
184
+ type: string
185
+ required: true
186
+ pattern: "^[A-Za-z0-9_-]{1,100}$"
187
+ description: The product's id, unique in the account, such as `pro-${ctx.scope}`.
188
+ example: "pro-${ctx.scope}"
189
+ name:
190
+ type: string
191
+ required: true
192
+ description: The product's name, shown to customers.
193
+ example: Pro (preview)
194
+ description:
195
+ type: string
196
+ description: The product's description, shown to customers.
197
+ http:
198
+ read: { path: "/products/{product_id}" }
199
+ create: { path: /products, idempotency_key: true, body: { id: { param: product_id } } }
200
+ update: { method: POST }
201
+ delete: { method: POST, body: { active: false } }
202
+ fields:
203
+ name: { param: name }
204
+ description: { param: description }
205
+ active: true
206
+ price:
207
+ kind: resource
208
+ title: Price
209
+ summary: "A test-mode price found by its lookup key (`lookup`, such as `pro-monthly-pr-42`, which the preview's code looks the price up by), product, currency, amount and interval. Amount, currency and interval cannot change on a Stripe price, so changing them in the plan makes a new price that takes over the lookup key, and the old one stays until the scope is destroyed. Destroy archives it (`active: false`): Stripe never deletes a price. A console edit of its nickname is drift."
210
+ covers: [stripe-test-objects]
211
+ params:
212
+ lookup:
213
+ type: string
214
+ required: true
215
+ pattern: "^\\S{1,200}$"
216
+ description: "The price's `lookup_key`: what the preview's code looks it up by, unique among active prices. (Named `lookup` because a plan key ending in `_key` must hold a secret reference.)"
217
+ example: "pro-monthly-${ctx.scope}"
218
+ product:
219
+ type: string
220
+ required: true
221
+ description: "The product's id: usually `{ from: <stripe.product line>.id }`."
222
+ example: pro-pr-42
223
+ currency:
224
+ type: string
225
+ required: true
226
+ pattern: "^[a-z]{3}$"
227
+ description: Three-letter ISO currency code, lower-case.
228
+ example: usd
229
+ unit_amount:
230
+ type: integer
231
+ required: true
232
+ description: The amount in the currency's smallest unit (1500 is 15.00 USD).
233
+ example: 1500
234
+ interval:
235
+ type: string
236
+ enum: [day, week, month, year]
237
+ description: The billing interval of a recurring price; left out, the price is one-time.
238
+ example: month
239
+ nickname:
240
+ type: string
241
+ description: A note on the price, hidden from customers.
242
+ example: Pro monthly (preview)
243
+ http:
244
+ find:
245
+ path: "/prices?lookup_keys[]={lookup}&active=true&limit=10"
246
+ list_path: /data
247
+ match:
248
+ lookup_key: { param: lookup }
249
+ active: true
250
+ product: { param: product }
251
+ currency: { param: currency }
252
+ unit_amount: { param: unit_amount }
253
+ /recurring/interval: { param: interval }
254
+ create: { path: /prices, idempotency_key: true, body: { transfer_lookup_key: true } }
255
+ update: { method: POST, path: "/prices/{id}" }
256
+ delete: { method: POST, path: "/prices/{id}", body: { active: false } }
257
+ fields:
258
+ nickname: { param: nickname }
259
+ live:
260
+ params: { product: SPONSON_LIVE_STRIPE_PRODUCT }
261
+ coupon:
262
+ kind: resource
263
+ title: Coupon
264
+ summary: "A test-mode coupon with an id of the plan's choosing, such as `preview-pr-42`, for the discount codes a preview accepts. Its discount (percent or amount, duration) is set on create and fixed after; give the line a new `coupon_id` to change it. Destroy deletes it. A console edit of its name is drift."
265
+ covers: [stripe-test-objects]
266
+ params:
267
+ coupon_id:
268
+ type: string
269
+ required: true
270
+ pattern: "^[A-Za-z0-9_-]{1,100}$"
271
+ description: The coupon's id (the code applied to a customer), unique in the account.
272
+ example: "preview-${ctx.scope}"
273
+ percent_off:
274
+ type: number
275
+ description: The discount in percent (0–100); or set `amount_off` and `currency`.
276
+ example: 25
277
+ amount_off:
278
+ type: integer
279
+ description: The discount in the currency's smallest unit, with `currency`.
280
+ currency:
281
+ type: string
282
+ pattern: "^[a-z]{3}$"
283
+ description: The currency of `amount_off`.
284
+ duration:
285
+ type: string
286
+ enum: [once, repeating, forever]
287
+ default: once
288
+ description: How long the discount applies to a subscription.
289
+ duration_in_months:
290
+ type: integer
291
+ description: "For `duration: repeating`, the number of months."
292
+ name:
293
+ type: string
294
+ description: The name shown to customers (the id when left out).
295
+ example: Preview discount
296
+ http:
297
+ read: { path: "/coupons/{coupon_id}" }
298
+ create:
299
+ path: /coupons
300
+ idempotency_key: true
301
+ body:
302
+ id: { param: coupon_id }
303
+ percent_off: { param: percent_off }
304
+ amount_off: { param: amount_off }
305
+ currency: { param: currency }
306
+ duration: { param: duration }
307
+ duration_in_months: { param: duration_in_months }
308
+ update: { method: POST }
309
+ fields:
310
+ name: { param: name }
@@ -0,0 +1,113 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Stytch Programmatic Workspace Actions (management API v3) recipes. Format and checklist: docs/adr/0020-recipes.md,
3
+ # CONTRIBUTING.md "Adding a recipe".
4
+ provider: stytch
5
+ title: Stytch
6
+ category: auth
7
+ verified_on: "2026-10-11"
8
+ docs:
9
+ - title: Create redirect URL (PWA v3)
10
+ url: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/create-redirect-url
11
+ - title: Get all redirect URLs (PWA v3)
12
+ url: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/get-all-redirect-urls
13
+ - title: Get redirect URL (PWA v3)
14
+ url: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/get-redirect-url
15
+ - title: Update redirect URL (PWA v3)
16
+ url: https://stytch.com/docs/workspace-management/pwa/update-redirect-url
17
+ - title: Delete redirect URL (PWA v3)
18
+ url: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/delete-redirect-url
19
+ - title: Limits
20
+ url: https://stytch.com/docs/workspace-management/pwa/limits
21
+ api:
22
+ base_url: https://management.stytch.com
23
+ base_url_env: STYTCH_MANAGEMENT_API_URL
24
+ auth: { basic: { user_env: STYTCH_WORKSPACE_KEY_ID, password_env: STYTCH_WORKSPACE_KEY_SECRET } }
25
+ detect:
26
+ deps: [stytch, "@stytch/"]
27
+ env: ["STYTCH_*"]
28
+ starter:
29
+ - op: redirect_url
30
+ where:
31
+ project_slug: "Stytch Dashboard → Project settings: the project's slug"
32
+ url: "the exact redirect URL Stytch receives on a preview: its URL plus the path (a `{ from }` reference cannot carry a path)"
33
+ assumptions:
34
+ - id: SY1
35
+ verified: true
36
+ text: "The management API authenticates with HTTP Basic: a workspace management key's id as the user and its secret as the password."
37
+ source: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/create-redirect-url
38
+ - id: SY2
39
+ verified: true
40
+ text: "`POST /pwa/v3/projects/{project_slug}/environments/{environment_slug}/redirect_urls` takes `url`, `valid_types` (`{ type, is_default }`, type one of LOGIN, INVITE, SIGNUP, RESET_PASSWORD, DISCOVERY) and `do_not_promote_defaults`, and answers `{ request_id, redirect_url: { url, valid_types }, status_code }`."
41
+ source: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/create-redirect-url
42
+ - id: SY3
43
+ verified: true
44
+ text: "`GET …/redirect_urls/all` answers `{ redirect_urls: [{ url, valid_types }], … }`: every redirect URL of the environment, unpaged."
45
+ source: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/get-all-redirect-urls
46
+ - id: SY4
47
+ verified: true
48
+ text: "A redirect URL has no id of its own: `GET`, `PUT` (to change `valid_types`) and `DELETE` on `…/redirect_urls?url=<url>` address it by its URL."
49
+ source: https://stytch.com/docs/api-reference/pwa/api/v3/redirect-urls/delete-redirect-url
50
+ - id: SY5
51
+ verified: true
52
+ text: "A project has at most 100 redirect URLs."
53
+ source: https://stytch.com/docs/workspace-management/pwa/limits
54
+ - id: SY6
55
+ verified: false
56
+ text: "`do_not_promote_defaults: true` on create and delete keeps a preview URL from becoming, or a deletion from changing, the environment's default for a type."
57
+ - id: SY7
58
+ verified: false
59
+ text: "A `PUT` body that also carries `url` (the adapter sends the identifying field back on PUT) is accepted, and `valid_types` read back in the order and form they were written, so an unchanged URL hashes the same on every plan."
60
+ - id: SY8
61
+ verified: false
62
+ text: "Each redirect URL is its own object (no shared list to rewrite), so concurrent previews need no lock."
63
+ ops:
64
+ redirect_url:
65
+ kind: resource
66
+ title: Redirect URL
67
+ summary: "One redirect URL of a Stytch project environment, such as the preview's `/authenticate`, with the redirect types it is valid for (by default login and sign-up, never the default). Found by its URL; a console edit of its types is drift and is put back with a `PUT`. Destroy deletes it without promoting another URL to default."
68
+ covers: [stytch-redirect]
69
+ params:
70
+ project_slug:
71
+ type: string
72
+ required: true
73
+ pattern: "^[A-Za-z0-9_-]+$"
74
+ description: The project's slug.
75
+ example: my-app
76
+ environment_slug:
77
+ type: string
78
+ required: true
79
+ pattern: "^[A-Za-z0-9_-]+$"
80
+ description: The environment's slug within the project (its test environment, or a custom one).
81
+ example: test
82
+ url:
83
+ type: string
84
+ required: true
85
+ pattern: "^https?://\\S+$"
86
+ description: "The redirect URL, exactly as Stytch will receive it (`https://pr-42.preview.example.com/authenticate`)."
87
+ example: "https://${ctx.scope}.preview.example.com/authenticate"
88
+ valid_types:
89
+ type: list
90
+ default: [{ type: LOGIN, is_default: false }, { type: SIGNUP, is_default: false }]
91
+ description: "The redirect types the URL is valid for: `{ type, is_default }` with type LOGIN, INVITE, SIGNUP, RESET_PASSWORD or DISCOVERY."
92
+ http:
93
+ find:
94
+ path: "/pwa/v3/projects/{project_slug}/environments/{environment_slug}/redirect_urls/all"
95
+ list_path: /redirect_urls
96
+ match: { url: { param: url } }
97
+ create:
98
+ path: "/pwa/v3/projects/{project_slug}/environments/{environment_slug}/redirect_urls"
99
+ body: { do_not_promote_defaults: true }
100
+ update:
101
+ method: PUT
102
+ path: "/pwa/v3/projects/{project_slug}/environments/{environment_slug}/redirect_urls?url={id}"
103
+ body: { do_not_promote_defaults: true }
104
+ delete:
105
+ path: "/pwa/v3/projects/{project_slug}/environments/{environment_slug}/redirect_urls?url={id}&do_not_promote_defaults=true"
106
+ item_path: /redirect_url
107
+ id_path: /url
108
+ fields:
109
+ valid_types: { param: valid_types }
110
+ sim:
111
+ item_query: url
112
+ live:
113
+ params: { project_slug: SPONSON_LIVE_STYTCH_PROJECT_SLUG, environment_slug: SPONSON_LIVE_STYTCH_ENVIRONMENT_SLUG }