@sponson/adapters 0.4.0 → 0.6.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 (129) hide show
  1. package/README.md +1 -1
  2. package/dist/algolia.d.ts +6 -0
  3. package/dist/algolia.js +151 -0
  4. package/dist/algolia.js.map +1 -0
  5. package/dist/clerk.js +1 -1
  6. package/dist/clerk.js.map +1 -1
  7. package/dist/cloud_run.d.ts +12 -0
  8. package/dist/cloud_run.js +469 -0
  9. package/dist/cloud_run.js.map +1 -0
  10. package/dist/cloudflare.d.ts +25 -0
  11. package/dist/cloudflare.js +310 -0
  12. package/dist/cloudflare.js.map +1 -0
  13. package/dist/common.d.ts +2 -6
  14. package/dist/common.js.map +1 -1
  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 +94 -0
  25. package/dist/http-adapter-auth.js +440 -0
  26. package/dist/http-adapter-auth.js.map +1 -0
  27. package/dist/http-adapter-client.d.ts +57 -0
  28. package/dist/http-adapter-client.js +163 -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 +61 -2
  46. package/dist/http.js +79 -18
  47. package/dist/http.js.map +1 -1
  48. package/dist/index.d.ts +13 -0
  49. package/dist/index.js +26 -2
  50. package/dist/index.js.map +1 -1
  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.js +1 -1
  58. package/dist/neon.js.map +1 -1
  59. package/dist/netlify.d.ts +18 -0
  60. package/dist/netlify.js +436 -0
  61. package/dist/netlify.js.map +1 -0
  62. package/dist/planetscale.d.ts +10 -0
  63. package/dist/planetscale.js +311 -0
  64. package/dist/planetscale.js.map +1 -0
  65. package/dist/providers.d.ts +28 -0
  66. package/dist/providers.js +41 -0
  67. package/dist/providers.js.map +1 -0
  68. package/dist/recipes.d.ts +167 -0
  69. package/dist/recipes.js +246 -0
  70. package/dist/recipes.js.map +1 -0
  71. package/dist/secrets.d.ts +44 -0
  72. package/dist/secrets.js +279 -0
  73. package/dist/secrets.js.map +1 -1
  74. package/dist/sigv4.d.ts +56 -0
  75. package/dist/sigv4.js +118 -0
  76. package/dist/sigv4.js.map +1 -0
  77. package/dist/supabase.d.ts +9 -0
  78. package/dist/supabase.js +385 -0
  79. package/dist/supabase.js.map +1 -0
  80. package/dist/vercel.js +1 -1
  81. package/dist/vercel.js.map +1 -1
  82. package/dist/xata.d.ts +8 -0
  83. package/dist/xata.js +142 -0
  84. package/dist/xata.js.map +1 -0
  85. package/dist/xml.d.ts +25 -0
  86. package/dist/xml.js +211 -0
  87. package/dist/xml.js.map +1 -0
  88. package/package.json +11 -4
  89. package/recipes/amplify.yaml +147 -0
  90. package/recipes/auth0.yaml +178 -0
  91. package/recipes/azure_swa.yaml +116 -0
  92. package/recipes/cloudflare.yaml +108 -0
  93. package/recipes/cockroachdb.yaml +132 -0
  94. package/recipes/configcat.yaml +123 -0
  95. package/recipes/datadog.yaml +136 -0
  96. package/recipes/firebase.yaml +84 -0
  97. package/recipes/flagsmith.yaml +98 -0
  98. package/recipes/fly.yaml +97 -0
  99. package/recipes/fly_machines.yaml +67 -0
  100. package/recipes/gcs.yaml +102 -0
  101. package/recipes/github.yaml +124 -0
  102. package/recipes/growthbook.yaml +124 -0
  103. package/recipes/heroku.yaml +98 -0
  104. package/recipes/honeycomb.yaml +89 -0
  105. package/recipes/inngest.yaml +97 -0
  106. package/recipes/kinde.yaml +120 -0
  107. package/recipes/mongodb_atlas.yaml +119 -0
  108. package/recipes/okta.yaml +177 -0
  109. package/recipes/posthog.yaml +127 -0
  110. package/recipes/postmark.yaml +105 -0
  111. package/recipes/postmark_server.yaml +93 -0
  112. package/recipes/prisma.yaml +150 -0
  113. package/recipes/qstash.yaml +134 -0
  114. package/recipes/railway.yaml +130 -0
  115. package/recipes/render.yaml +103 -0
  116. package/recipes/resend.yaml +130 -0
  117. package/recipes/s3.yaml +108 -0
  118. package/recipes/sendgrid.yaml +165 -0
  119. package/recipes/sentry.yaml +158 -0
  120. package/recipes/split.yaml +121 -0
  121. package/recipes/statsig.yaml +124 -0
  122. package/recipes/stripe.yaml +310 -0
  123. package/recipes/stytch.yaml +113 -0
  124. package/recipes/svix.yaml +109 -0
  125. package/recipes/turso.yaml +98 -0
  126. package/recipes/unleash.yaml +149 -0
  127. package/recipes/upstash.yaml +130 -0
  128. package/recipes/vercel.yaml +116 -0
  129. package/recipes/workos.yaml +70 -0
@@ -0,0 +1,89 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Honeycomb API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: honeycomb
4
+ title: Honeycomb
5
+ category: observability
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: Create a Marker
9
+ url: https://docs.honeycomb.io/api/markers/create-a-marker
10
+ - title: List All Markers
11
+ url: https://docs.honeycomb.io/api/markers
12
+ - title: Update a Marker
13
+ url: https://docs.honeycomb.io/api/markers/update-a-marker
14
+ - title: Delete a Marker
15
+ url: https://docs.honeycomb.io/api/markers/delete-a-marker
16
+ api:
17
+ base_url: https://api.honeycomb.io
18
+ base_url_env: HONEYCOMB_API_URL
19
+ auth: { header: X-Honeycomb-Team, value_env: HONEYCOMB_API_KEY }
20
+ detect:
21
+ deps: ["@honeycombio/"]
22
+ env: ["HONEYCOMB_*"]
23
+ starter:
24
+ - op: marker
25
+ values: { dataset: __all__, message: "deploy ${ctx.scope} ${ctx.git.sha}" }
26
+ assumptions:
27
+ - id: HC1
28
+ verified: true
29
+ text: "`POST /1/markers/{datasetSlug}` creates a marker from `start_time`, `end_time` (Unix seconds), `message`, `type` and `url` (all optional; `start_time` defaults to the request's arrival), answering 201 with the marker and its `id`; `__all__` as the dataset slug makes an environment-wide marker. Requests carry a Configuration Key with the Manage Markers permission in `X-Honeycomb-Team`."
30
+ source: https://docs.honeycomb.io/api/markers/create-a-marker
31
+ - id: HC2
32
+ verified: true
33
+ text: "`GET /1/markers/{datasetSlug}` answers a bare array of the dataset's markers (`id`, `message`, `type`, `url`, `start_time`, `end_time`, `created_at`, `updated_at`, `color`)."
34
+ source: https://docs.honeycomb.io/api/markers
35
+ - id: HC3
36
+ verified: true
37
+ text: "`PUT /1/markers/{datasetSlug}/{markerId}` replaces the marker: a field left out of the body is erased."
38
+ source: https://docs.honeycomb.io/api/markers/update-a-marker
39
+ - id: HC4
40
+ verified: true
41
+ text: "`DELETE /1/markers/{datasetSlug}/{markerId}` removes the marker and answers it (200)."
42
+ source: https://docs.honeycomb.io/api/markers/delete-a-marker
43
+ - id: HC5
44
+ verified: true
45
+ text: "The API is served from `api.honeycomb.io` (US) and `api.eu1.honeycomb.io` (EU); EU teams set `base_url` in the provider block."
46
+ source: https://docs.honeycomb.io/api/markers/create-a-marker
47
+ - id: HC6
48
+ verified: false
49
+ text: "Message and type identify the marker this recipe created among the dataset's markers (Honeycomb does not require them to be unique: two equal ones make `find` refuse with PARAM_INVALID), and the list holds every marker of the dataset in one answer."
50
+ - id: HC7
51
+ verified: false
52
+ text: "An update (a changed `url`) re-sends message, type and url but not `start_time`, which Honeycomb then sets to the time of the update (HC3), moving the marker; markers are rarely edited."
53
+ - id: HC8
54
+ verified: false
55
+ text: "A deleted marker answers 404 to DELETE, which destroy treats as already gone."
56
+ ops:
57
+ marker:
58
+ kind: resource
59
+ title: Deploy marker
60
+ summary: "A marker on an environment's dataset (or on every dataset of the environment with `__all__`) for a deploy, such as `deploy pr-42 0123abc`. Located by its message and type; a console edit of its URL is drift. Destroying the scope removes it; write `destroy: keep` to keep the deploy history."
61
+ covers: [honeycomb-marker]
62
+ params:
63
+ dataset:
64
+ type: string
65
+ required: true
66
+ description: The dataset's slug, or `__all__` for an environment-wide marker.
67
+ example: __all__
68
+ message:
69
+ type: string
70
+ required: true
71
+ description: The marker's text; with its type, its identity (HC6).
72
+ example: "deploy ${ctx.scope} ${ctx.git.sha}"
73
+ type:
74
+ type: string
75
+ default: deploy
76
+ description: The marker's type, which groups markers (and their color).
77
+ url:
78
+ type: string
79
+ description: The link opened from the marker (the preview or the build).
80
+ example: "https://${ctx.scope}.preview.example.com"
81
+ http:
82
+ find:
83
+ path: "/1/markers/{dataset}"
84
+ match: { message: { param: message }, type: { param: type } }
85
+ create: { path: "/1/markers/{dataset}" }
86
+ update: { method: PUT, path: "/1/markers/{dataset}/{id}" }
87
+ delete: { path: "/1/markers/{dataset}/{id}" }
88
+ fields:
89
+ url: { param: url }
@@ -0,0 +1,97 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Inngest recipes. Format and checklist: docs/adr/0020-recipes.md, docs/adr/0022-one-off-actions.md, CONTRIBUTING.md
3
+ # "Adding a recipe". No API block: the sync is a request to the app's own serve endpoint, which registers itself
4
+ # with Inngest using its own signing key, so Sponson sends no Inngest credential at all.
5
+ provider: inngest
6
+ title: Inngest
7
+ category: queues
8
+ verified_on: "2026-10-11"
9
+ docs:
10
+ - title: Syncing an Inngest app (curl -X PUT on the serve endpoint), Inngest's integration guide
11
+ url: https://www.inngest.com/llm-context.md
12
+ - title: Apps in Inngest Cloud (sync methods, when to sync)
13
+ url: https://www.inngest.com/docs/apps/cloud
14
+ - title: Environments and branch environments
15
+ url: https://www.inngest.com/docs/platform/environments
16
+ - title: The TypeScript SDK's serve handler (InngestCommHandler, PUT = register)
17
+ url: https://github.com/inngest/inngest-js/blob/main/packages/inngest/src/components/InngestCommHandler.ts
18
+ - title: REST API v2 OpenAPI document (V2_SyncApp)
19
+ url: https://api-docs.inngest.com/api-specs/v2.json
20
+ detect:
21
+ deps: [inngest]
22
+ env: ["INNGEST_*"]
23
+ starter:
24
+ - op: sync
25
+ preview_url: url
26
+ values: { revision: "${ctx.git.sha}" }
27
+ assumptions:
28
+ - id: IN1
29
+ verified: true
30
+ text: "A `PUT` to the app's serve endpoint (by convention `/api/inngest`, `curl -X PUT https://your-app.com/api/inngest --fail-with-body`) makes the app's SDK register its functions with Inngest; the SDK authenticates that registration with the app's own `INNGEST_SIGNING_KEY`, so the caller sends no credential."
31
+ source: https://www.inngest.com/llm-context.md
32
+ - id: IN2
33
+ verified: true
34
+ text: "The app registers into the environment its SDK is configured for: `INNGEST_ENV` (or the client's `env` option) names the branch environment and always wins; Vercel's `VERCEL_GIT_COMMIT_REF` is detected automatically, while Netlify, Cloudflare Pages, Railway and Render must pass their branch variable. Sponson does not choose the environment: the app must be configured so."
35
+ source: https://www.inngest.com/docs/platform/environments
36
+ - id: IN3
37
+ verified: true
38
+ text: "A branch environment is created on demand the first time an app syncs with it (or events are sent to it), and every branch environment shares one signing key and one event key, so a preview needs no per-branch key."
39
+ source: https://www.inngest.com/docs/platform/environments
40
+ - id: IN4
41
+ verified: true
42
+ text: "Without `x-inngest-sync-kind: in-band` (which this recipe never sends) the serve handler syncs out of band: it calls Inngest's register endpoint itself and answers with that call's status and a JSON body `{ message, modified }`; a failed registration answers 500 (`Failed to register`)."
43
+ source: https://github.com/inngest/inngest-js/blob/main/packages/inngest/src/components/InngestCommHandler.ts
44
+ - id: IN5
45
+ verified: true
46
+ text: "Sync only once the new version of the code is live: some hosts roll a deploy out over seconds or minutes and an early sync registers the old functions. The recipe's trigger is the deployment's URL (a deploy line's external output, so the line waits for the deploy) and a revision."
47
+ source: https://www.inngest.com/docs/apps/cloud
48
+ - id: IN6
49
+ verified: true
50
+ text: "Branch environments are archived automatically three days after their latest deployment (archiving stops functions from triggering and keeps the data), and archiving cannot be undone per app; there is no request to undo a sync, so destroy sends nothing and forgets the record."
51
+ source: https://www.inngest.com/docs/platform/environments
52
+ - id: IN7
53
+ verified: true
54
+ text: "The REST API's `POST /v2/apps/{appId}/syncs` (marked beta, `x-beta: true`) takes `{ url }` and answers a sync with `status` pending, success, error or duplicate, but the operation declares no header or parameter that selects a branch environment; this recipe does not use it."
55
+ source: https://api-docs.inngest.com/api-specs/v2.json
56
+ - id: IN8
57
+ verified: false
58
+ text: "Syncing the same deployment again is harmless (the same functions registered again, `modified: false`), so the at-least-once delivery of an action (sent again after a crash before it was recorded) needs no idempotency key."
59
+ - id: IN9
60
+ verified: false
61
+ text: "A preview behind Vercel Deployment Protection answers the `PUT` with 401 unless it carries `x-vercel-protection-bypass` with the project's automation bypass secret; pass it with `headers`, as a `{ secret }`."
62
+ ops:
63
+ sync:
64
+ kind: action
65
+ title: App sync into its branch environment
66
+ summary: Registers a preview deployment's Inngest functions by sending `PUT` to its serve endpoint, once per deployment URL and revision; the app's SDK picks the branch environment (`INNGEST_ENV`, IN2) and signs the registration with its own key (IN1), so no Inngest credential is needed.
67
+ covers: [inngest-sync]
68
+ params:
69
+ url:
70
+ type: string
71
+ description: "The deployment's base URL, without a trailing slash: usually `{ from: <deploy line>.preview_url }`, so the sync waits for the deploy (IN5)."
72
+ required: true
73
+ pattern: "^https?://\\S+[^/]$"
74
+ example: https://acme-git-feat-x-acme.vercel.app
75
+ path:
76
+ type: string
77
+ description: Where the app serves Inngest's `serve()` handler, appended to `url`.
78
+ default: /api/inngest
79
+ pattern: "^/\\S*$"
80
+ revision:
81
+ type: string
82
+ description: "What else makes it sync again when it changes, usually the commit: `\"${ctx.git.sha}\"`."
83
+ required: true
84
+ example: "${ctx.git.sha}"
85
+ headers:
86
+ type: map
87
+ description: "Extra request headers, such as `x-vercel-protection-bypass: { secret: env://VERCEL_AUTOMATION_BYPASS_SECRET }` for a protected preview (IN9)."
88
+ http:
89
+ trigger: { url: { param: url }, path: { param: path }, revision: { param: revision } }
90
+ request:
91
+ method: PUT
92
+ url: { join: [{ param: url }, { param: path }] }
93
+ headers: { param: headers }
94
+ outputs: { modified: /modified }
95
+ live:
96
+ # A deployed app with Inngest's serve handler and INNGEST_ENV set to a throwaway branch environment.
97
+ params: { url: SPONSON_LIVE_INNGEST_APP_URL }
@@ -0,0 +1,120 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Kinde Management API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: kinde
4
+ title: Kinde
5
+ category: auth
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: Kinde Management API (OpenAPI specification)
9
+ url: https://api-spec.kinde.com/kinde-management-api-spec.yaml
10
+ - title: Access the Kinde Management API
11
+ url: https://docs.kinde.com/developer-tools/kinde-api/connect-to-kinde-api/
12
+ - title: Kinde Management API JS SDK (conventional variable names)
13
+ url: https://github.com/kinde-oss/management-api-js
14
+ api:
15
+ base_url_env: KINDE_API_URL
16
+ auth:
17
+ oauth2_client_credentials:
18
+ token_url: /oauth2/token
19
+ token_url_env: KINDE_TOKEN_URL
20
+ client_id_env: KINDE_MANAGEMENT_CLIENT_ID
21
+ client_secret_env: KINDE_MANAGEMENT_CLIENT_SECRET
22
+ audience: /api
23
+ base_url_example: https://acme.kinde.com/api/v1
24
+ detect:
25
+ deps: ["@kinde-oss/"]
26
+ env: ["KINDE_*"]
27
+ starter:
28
+ - op: callback_url
29
+ - op: logout_url
30
+ preview_url: url
31
+ where:
32
+ base_url: "`https://<business>.kinde.com/api/v1`: Kinde → Settings → Details → Domain"
33
+ app_id: "Kinde → Settings → Applications → your application → Details → Client ID"
34
+ url: "the exact callback URL the SDK sends on a preview: its URL plus the callback path (a `{ from }` reference cannot carry a path)"
35
+ assumptions:
36
+ - id: KI1
37
+ verified: true
38
+ text: "A machine-to-machine application authorized for the Management API gets a token from `POST https://<business>.kinde.com/oauth2/token` (form-encoded `grant_type=client_credentials`, `client_id`, `client_secret`, `audience=https://<business>.kinde.com/api`); API calls go to `https://<business>.kinde.com/api/v1/…`. The recipe writes the token URL and audience as paths, so a plan's `base_url: https://<business>.kinde.com/api/v1` names the business once."
39
+ source: https://docs.kinde.com/developer-tools/kinde-api/connect-to-kinde-api/
40
+ - id: KI2
41
+ verified: true
42
+ text: "`GET /api/v1/applications/{app_id}/auth_redirect_urls` (scope `read:applications_redirect_uris`) answers `{ redirect_urls: [...] }`; `PUT` on the same path (scope `update:applications_redirect_uris`) replaces all of an application's callback URLs with the body's `urls` array. (`POST` adds and `DELETE ?urls=` removes; the recipe replaces the list it read, under a lock, so that one apply is one idempotent write.)"
43
+ source: https://api-spec.kinde.com/kinde-management-api-spec.yaml
44
+ - id: KI3
45
+ verified: true
46
+ text: "`GET /api/v1/applications/{app_id}/auth_logout_urls` answers `{ logout_urls: [...] }` and `PUT` replaces them with the body's `urls` array, as for callback URLs."
47
+ source: https://api-spec.kinde.com/kinde-management-api-spec.yaml
48
+ - id: KI4
49
+ verified: true
50
+ text: "The audience is always the business's kinde.com subdomain (`https://<business>.kinde.com/api`), even when a custom domain serves sign-in, so `base_url` names the kinde.com subdomain."
51
+ source: https://docs.kinde.com/developer-tools/kinde-api/connect-to-kinde-api/
52
+ - id: KI5
53
+ verified: true
54
+ text: "`KINDE_MANAGEMENT_CLIENT_ID` and `KINDE_MANAGEMENT_CLIENT_SECRET` are the variables Kinde's Management API SDK reads for the machine-to-machine client."
55
+ source: https://github.com/kinde-oss/management-api-js
56
+ - id: KI6
57
+ verified: false
58
+ text: "A `PUT` with an empty `urls` list is accepted and leaves the application with no URLs of that kind (destroying the last preview URL of an application that has no others)."
59
+ - id: KI7
60
+ verified: false
61
+ text: "URLs are read back exactly as written (no normalisation), and the replace has no precondition: the line locks the list (`lockOn`) against other Sponson runs; a console edit made between the read and the write can still be lost."
62
+ ops:
63
+ callback_url:
64
+ kind: list_item
65
+ title: Application callback URL
66
+ summary: "One URL in a Kinde application's Allowed callback URLs, such as the preview's `/api/auth/kinde_callback`. Read-modify-write: the list is read (`redirect_urls`) and replaced (`urls`) with the URL added, under a lock on the list; removed on destroy; every other URL is left as it is."
67
+ covers: [kinde-callbacks]
68
+ params:
69
+ app_id:
70
+ type: string
71
+ required: true
72
+ description: The application's id (its Client ID in the Kinde dashboard).
73
+ example: 3b0b5c6c8fcc464fab397f4969b5f482
74
+ url:
75
+ type: string
76
+ required: true
77
+ pattern: "^https?://\\S+$"
78
+ description: "The callback URL, exactly as the SDK sends it (`KINDE_REDIRECT_URL` / `KINDE_SITE_URL` + the callback path)."
79
+ example: "https://${ctx.scope}.preview.example.app/api/auth/kinde_callback"
80
+ http:
81
+ parent:
82
+ path: "/applications/{app_id}/auth_redirect_urls"
83
+ method: PUT
84
+ write_list_path: /urls
85
+ list_path: /redirect_urls
86
+ item: { param: url }
87
+ sim:
88
+ objects:
89
+ "/applications/{app_id}/auth_redirect_urls": { redirect_urls: [https://app.example.com/api/auth/kinde_callback] }
90
+ live:
91
+ params: { app_id: SPONSON_LIVE_KINDE_APP_ID }
92
+ logout_url:
93
+ kind: list_item
94
+ title: Application allowed logout redirect URL
95
+ summary: "One URL in a Kinde application's Allowed logout redirect URLs, such as the preview's home page. Read (`logout_urls`) and replaced (`urls`) with the URL added, under a lock on the list; removed on destroy; every other URL is left as it is."
96
+ covers: [kinde-callbacks]
97
+ params:
98
+ app_id:
99
+ type: string
100
+ required: true
101
+ description: The application's id (its Client ID in the Kinde dashboard).
102
+ example: 3b0b5c6c8fcc464fab397f4969b5f482
103
+ url:
104
+ type: string
105
+ required: true
106
+ pattern: "^https?://\\S+$"
107
+ description: "The URL the SDK redirects to after logout (`KINDE_POST_LOGOUT_REDIRECT_URL`)."
108
+ example: "https://${ctx.scope}.preview.example.app"
109
+ http:
110
+ parent:
111
+ path: "/applications/{app_id}/auth_logout_urls"
112
+ method: PUT
113
+ write_list_path: /urls
114
+ list_path: /logout_urls
115
+ item: { param: url }
116
+ sim:
117
+ objects:
118
+ "/applications/{app_id}/auth_logout_urls": { logout_urls: [https://app.example.com] }
119
+ live:
120
+ params: { app_id: SPONSON_LIVE_KINDE_APP_ID }
@@ -0,0 +1,119 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # MongoDB Atlas Administration API v2 recipes (programmatic API keys, HTTP Digest). Format and checklist:
3
+ # docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
4
+ provider: mongodb_atlas
5
+ title: MongoDB Atlas
6
+ category: databases
7
+ verified_on: "2026-10-11"
8
+ docs:
9
+ - title: Atlas Administration API v2 (OpenAPI document)
10
+ url: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2.json
11
+ - title: Create One Database User in One Project
12
+ url: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/operation/operation-creategroupdatabaseuser
13
+ - title: Configure Atlas API access (API keys use HTTP Digest)
14
+ url: https://www.mongodb.com/docs/atlas/configure-api-access/
15
+ - title: Atlas CLI environment variables
16
+ url: https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-env-variables/
17
+ api:
18
+ base_url: https://cloud.mongodb.com/api/atlas/v2
19
+ base_url_env: MONGODB_ATLAS_API_URL
20
+ media_type: application/vnd.atlas.2023-01-01+json
21
+ auth: { digest: { user_env: MONGODB_ATLAS_PUBLIC_API_KEY, password_env: MONGODB_ATLAS_PRIVATE_API_KEY } }
22
+ detect:
23
+ env: ["MONGODB_ATLAS_*"]
24
+ db_hosts: [.mongodb.net]
25
+ starter:
26
+ - op: database_user
27
+ values: { username: "app-${ctx.scope}", roles: [{ databaseName: "app_${ctx.scope}", roleName: readWrite }] }
28
+ where:
29
+ group_id: "Atlas → Project Settings → Project ID, or `atlas projects list`"
30
+ password: "a reference, never a value: `{ secret: \"env://PREVIEW_DB_PASSWORD\" }`"
31
+ assumptions:
32
+ - id: MA1
33
+ verified: true
34
+ text: "Programmatic API keys authenticate with HTTP Digest (the public key as the user name, the private key as the password); service accounts take an OAuth2 client-credentials token instead (`https://cloud.mongodb.com/api/oauth/token`)."
35
+ source: https://www.mongodb.com/docs/atlas/configure-api-access/
36
+ - id: MA2
37
+ verified: true
38
+ text: "The Atlas CLI keeps the key pair in `MONGODB_ATLAS_PUBLIC_API_KEY` and `MONGODB_ATLAS_PRIVATE_API_KEY`."
39
+ source: https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-env-variables/
40
+ - id: MA3
41
+ verified: true
42
+ text: "Requests and answers use the versioned media type `application/vnd.atlas.2023-01-01+json` (in `Accept` and `Content-Type`); the database-user operations are published under that version."
43
+ source: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2.json
44
+ - id: MA4
45
+ verified: true
46
+ text: "`POST /groups/{groupId}/databaseUsers` creates a user from `databaseName`, `groupId`, `roles` and `username` (required), with `password` for SCRAM users (write-only, never answered), optional `scopes` (absent: every cluster of the project) and `deleteAfterDate` (Atlas deletes the user then; within one week of the request). It answers 201 with the user."
47
+ source: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2.json
48
+ - id: MA5
49
+ verified: true
50
+ text: "`GET`, `PATCH` and `DELETE /groups/{groupId}/databaseUsers/{databaseName}/{username}` read, update and delete one user; SCRAM users authenticate against `databaseName` `admin`."
51
+ source: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2.json
52
+ - id: MA6
53
+ verified: true
54
+ text: "A project supports 100 database users by default (900 at most)."
55
+ source: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/operation/operation-creategroupdatabaseuser
56
+ - id: MA7
57
+ verified: false
58
+ text: "A GET of a user that does not exist answers 404; a second create of the same user answers 409."
59
+ - id: MA8
60
+ verified: false
61
+ text: "`roles` (each `{ databaseName, roleName, collectionName? }`) and `scopes` (each `{ name, type }`) are answered in the order they were sent, so they compare without drift."
62
+ - id: MA9
63
+ verified: false
64
+ text: "`PATCH` takes the user's identifying fields (`databaseName`, `groupId`, `username`) with the changed ones and keeps the password when none is sent."
65
+ ops:
66
+ database_user:
67
+ kind: resource
68
+ title: Database user
69
+ summary: "A SCRAM database user per scope on a shared Atlas cluster, such as `app-pr-42` with `readWrite` on the database `app_pr_42`, instead of a cluster per preview. The password is an input: a `{ secret: … }` reference (a value per environment in a secret manager) or a sensitive output of another line, sent on create only and never compared. Located by its user name; a console edit of its roles or scopes is drift; destroy deletes the user (not the database's data)."
70
+ covers: [mongodb-atlas-user]
71
+ params:
72
+ group_id:
73
+ type: string
74
+ required: true
75
+ pattern: "^[0-9a-f]{24}$"
76
+ description: The project (group) id, 24 hexadecimal digits.
77
+ example: 32b6e34b3d91647abb20e7b8
78
+ username:
79
+ type: string
80
+ required: true
81
+ description: The user name, unique in the project; its identity.
82
+ example: "app-${ctx.scope}"
83
+ password:
84
+ type: string
85
+ required: true
86
+ description: "The user's password, as a reference (`{ secret: env://… }`, or `{ from: <line>.<output> }` of a sensitive output); sent on create only (MA4)."
87
+ example: { secret: "env://PREVIEW_DB_PASSWORD" }
88
+ roles:
89
+ type: list
90
+ required: true
91
+ description: "The roles, each `{ databaseName, roleName }` (`readWrite`, `read`, `dbAdmin`, …)."
92
+ example: [{ databaseName: "app_${ctx.scope}", roleName: readWrite }]
93
+ scopes:
94
+ type: list
95
+ description: "The clusters it may reach, each `{ name, type: CLUSTER }`; absent: every cluster of the project (not compared then)."
96
+ delete_after:
97
+ type: string
98
+ description: "An ISO 8601 time, within a week, at which Atlas deletes the user by itself (sent on create only)."
99
+ http:
100
+ read: { path: "/groups/{group_id}/databaseUsers/admin/{username}" }
101
+ create:
102
+ path: "/groups/{group_id}/databaseUsers"
103
+ body:
104
+ databaseName: admin
105
+ groupId: { param: group_id }
106
+ username: { param: username }
107
+ password: { param: password }
108
+ deleteAfterDate: { param: delete_after }
109
+ update:
110
+ method: PATCH
111
+ body: { databaseName: admin, groupId: { param: group_id }, username: { param: username } }
112
+ id_path: /username
113
+ fields:
114
+ roles: { param: roles }
115
+ scopes: { param: scopes }
116
+ sim:
117
+ id_from: username
118
+ live:
119
+ params: { group_id: SPONSON_LIVE_ATLAS_GROUP_ID }
@@ -0,0 +1,177 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Okta Management API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: okta
4
+ title: Okta
5
+ category: auth
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: Okta Management API (OpenAPI specification, management-minimal.yaml)
9
+ url: https://raw.githubusercontent.com/okta/okta-management-openapi-spec/master/dist/current/management-minimal.yaml
10
+ - title: Applications API, replace an application
11
+ url: https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/#tag/Application/operation/replaceApplication
12
+ - title: Trusted Origins API
13
+ url: https://developer.okta.com/docs/api/openapi/okta-management/management/tag/TrustedOrigin/
14
+ - title: Okta Terraform provider, configuration (conventional variable names)
15
+ url: https://raw.githubusercontent.com/okta/terraform-provider-okta/master/docs/index.md
16
+ api:
17
+ base_url_env: OKTA_API_URL
18
+ auth: { header: Authorization, value_env: OKTA_API_TOKEN, prefix: "SSWS " }
19
+ base_url_example: https://acme.okta.com/api/v1
20
+ detect:
21
+ deps: ["@okta/"]
22
+ env: ["OKTA_*"]
23
+ starter:
24
+ - op: redirect_uri
25
+ - op: trusted_origin
26
+ preview_url: origin
27
+ values: { name: "sponson-${ctx.scope}" }
28
+ where:
29
+ base_url: "`https://<org>.okta.com/api/v1`: your Okta org's domain"
30
+ app_id: "Okta Admin Console → Applications → your application: the `0oa…` id in its URL"
31
+ url: "the exact redirect URI the app sends on a preview: its URL plus the callback path (a `{ from }` reference cannot carry a path)"
32
+ assumptions:
33
+ - id: OK1
34
+ verified: true
35
+ text: "An API token is sent as `Authorization: SSWS <token>` (the `apiToken` security scheme; Okta recommends scoped OAuth tokens, which need `private_key_jwt` and are not supported here). Every path is under `https://<org>.okta.com/api/v1`."
36
+ source: https://raw.githubusercontent.com/okta/okta-management-openapi-spec/master/dist/current/management-minimal.yaml
37
+ - id: OK2
38
+ verified: true
39
+ text: "`PUT /api/v1/apps/{appId}` replaces an application: all required properties must be in the body (no partial update), and system-assigned properties (`id`, `name`, `status`, `created`, `lastUpdated`) in the body are ignored. So the recipe sends the whole application read with `GET /api/v1/apps/{appId}` back, with one URI added or removed (`parent.send: parent`)."
40
+ source: https://raw.githubusercontent.com/okta/okta-management-openapi-spec/master/dist/current/management-minimal.yaml
41
+ - id: OK3
42
+ verified: true
43
+ text: "An OIDC application keeps its redirect URIs in `settings.oauthClient.redirect_uris` and its sign-out redirect URIs in `settings.oauthClient.post_logout_redirect_uris`, both arrays of strings."
44
+ source: https://raw.githubusercontent.com/okta/okta-management-openapi-spec/master/dist/current/management-minimal.yaml
45
+ - id: OK4
46
+ verified: false
47
+ text: "The read-only members of the GET answer that the replace documentation does not list (`_links`, `_embedded`, `credentials.oauthClient.client_id`) are ignored when sent back, as the listed system-assigned ones are."
48
+ - id: OK5
49
+ verified: false
50
+ text: "The application replace has no precondition: the line locks the application (`lockOn`, shared by both URI ops) against other Sponson runs; a console edit made between the read and the write can still be lost (the next apply reports it as drift)."
51
+ - id: OK6
52
+ verified: true
53
+ text: "`POST /api/v1/trustedOrigins` creates a trusted origin from `name`, `origin` and `scopes` (`[{ type: CORS }, { type: REDIRECT }]`, or `IFRAME_EMBED`); `GET /api/v1/trustedOrigins` lists them as a bare array, `q` prefix-matching `name` and `origin`, `limit` up to 200; `PUT /api/v1/trustedOrigins/{id}` replaces one; `DELETE /api/v1/trustedOrigins/{id}` answers 204. `name` and `origin` are each unique."
54
+ source: https://raw.githubusercontent.com/okta/okta-management-openapi-spec/master/dist/current/management-minimal.yaml
55
+ - id: OK7
56
+ verified: false
57
+ text: "A trusted origin is found among the first 200 that `q=<origin>` matches (pagination follows a `Link` header, which the http adapter does not read); an org with more than 200 origins starting with the same text may not find it and would see the create refused as a duplicate."
58
+ - id: OK8
59
+ verified: false
60
+ text: "Scopes are read back as written (`[{ type: CORS }, { type: REDIRECT }]`, no added members), so they can be compared as drift."
61
+ - id: OK9
62
+ verified: true
63
+ text: "`OKTA_API_TOKEN` is the variable the Okta Terraform provider reads for an API token."
64
+ source: https://raw.githubusercontent.com/okta/terraform-provider-okta/master/docs/index.md
65
+ ops:
66
+ redirect_uri:
67
+ kind: list_item
68
+ title: OIDC application sign-in redirect URI
69
+ summary: "One URI in an Okta OIDC application's sign-in redirect URIs (`settings.oauthClient.redirect_uris`), such as the preview's `/login/callback`. The application is read, and written back whole with the URI added (Okta replaces applications only as a whole), under a lock on the application; removed on destroy; every other URI and setting is sent back as read."
70
+ covers: [okta-redirect]
71
+ params:
72
+ app_id:
73
+ type: string
74
+ required: true
75
+ description: The application's id (`0oa…`).
76
+ example: 0oa1gjh63g214q0Hq0g4
77
+ url:
78
+ type: string
79
+ required: true
80
+ pattern: "^\\S+://\\S+$"
81
+ description: "The redirect URI, exactly as the application sends it as `redirect_uri`."
82
+ example: "https://${ctx.scope}.preview.example.app/login/callback"
83
+ http:
84
+ parent: { path: "/apps/{app_id}", method: PUT, send: parent }
85
+ list_path: /settings/oauthClient/redirect_uris
86
+ item: { param: url }
87
+ sim:
88
+ objects:
89
+ "/apps/{app_id}":
90
+ id: 0oa1gjh63g214q0Hq0g4
91
+ name: oidc_client
92
+ label: Web app
93
+ status: ACTIVE
94
+ signOnMode: OPENID_CONNECT
95
+ credentials: { oauthClient: { client_id: 0oa1gjh63g214q0Hq0g4, token_endpoint_auth_method: client_secret_basic } }
96
+ settings:
97
+ oauthClient:
98
+ application_type: web
99
+ grant_types: [authorization_code]
100
+ response_types: [code]
101
+ redirect_uris: [https://app.example.com/login/callback]
102
+ post_logout_redirect_uris: [https://app.example.com]
103
+ live:
104
+ params: { app_id: SPONSON_LIVE_OKTA_APP_ID }
105
+ post_logout_redirect_uri:
106
+ kind: list_item
107
+ title: OIDC application sign-out redirect URI
108
+ summary: "One URI in an Okta OIDC application's sign-out redirect URIs (`settings.oauthClient.post_logout_redirect_uris`), such as the preview's home page. Written back with the whole application, under the application's lock; removed on destroy; every other URI and setting is sent back as read."
109
+ covers: [okta-redirect]
110
+ params:
111
+ app_id:
112
+ type: string
113
+ required: true
114
+ description: The application's id (`0oa…`).
115
+ example: 0oa1gjh63g214q0Hq0g4
116
+ url:
117
+ type: string
118
+ required: true
119
+ pattern: "^\\S+://\\S+$"
120
+ description: "The URI the application passes as `post_logout_redirect_uri`."
121
+ example: "https://${ctx.scope}.preview.example.app"
122
+ http:
123
+ parent: { path: "/apps/{app_id}", method: PUT, send: parent }
124
+ list_path: /settings/oauthClient/post_logout_redirect_uris
125
+ item: { param: url }
126
+ sim:
127
+ objects:
128
+ "/apps/{app_id}":
129
+ id: 0oa1gjh63g214q0Hq0g4
130
+ name: oidc_client
131
+ label: Web app
132
+ status: ACTIVE
133
+ signOnMode: OPENID_CONNECT
134
+ credentials: { oauthClient: { client_id: 0oa1gjh63g214q0Hq0g4, token_endpoint_auth_method: client_secret_basic } }
135
+ settings:
136
+ oauthClient:
137
+ application_type: web
138
+ grant_types: [authorization_code]
139
+ response_types: [code]
140
+ redirect_uris: [https://app.example.com/login/callback]
141
+ post_logout_redirect_uris: [https://app.example.com]
142
+ live:
143
+ params: { app_id: SPONSON_LIVE_OKTA_APP_ID }
144
+ trusted_origin:
145
+ kind: resource
146
+ title: Trusted origin (CORS and redirect)
147
+ summary: "A trusted origin for the preview, so the Okta Sign-In Widget and Auth JS may call Okta from it (CORS) and Okta may redirect to it after sign-in or sign-out (REDIRECT). Found by its origin; its name and scopes are compared as drift and replaced with `PUT`; deleted on destroy."
148
+ covers: [okta-redirect]
149
+ params:
150
+ name:
151
+ type: string
152
+ required: true
153
+ description: "The trusted origin's name, unique in the org."
154
+ example: "sponson-${ctx.scope}"
155
+ origin:
156
+ type: string
157
+ required: true
158
+ pattern: "^\\S+://[^/\\s]+$"
159
+ description: "The origin, scheme and host without a path (`https://pr-42.preview.example.app`); unique in the org."
160
+ example: "https://${ctx.scope}.preview.example.app"
161
+ scopes:
162
+ type: list
163
+ default: [{ type: CORS }, { type: REDIRECT }]
164
+ description: "What the origin is trusted for: `[{ type: CORS }]`, `[{ type: REDIRECT }]`, both (the default), or `IFRAME_EMBED`."
165
+ http:
166
+ find:
167
+ path: "/trustedOrigins?q={origin}&limit=200"
168
+ list_path: ""
169
+ match: { origin: { param: origin } }
170
+ create: { path: /trustedOrigins }
171
+ update: { method: PUT, path: "/trustedOrigins/{id}" }
172
+ delete: { path: "/trustedOrigins/{id}" }
173
+ item_path: ""
174
+ fields:
175
+ name: { param: name }
176
+ origin: { param: origin }
177
+ scopes: { param: scopes }