create-ailk 0.23.0 → 0.25.1
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/README.md +68 -33
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +10 -3
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +38 -4
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +75 -22
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +1 -1
- package/dist/lib/fetch-template-snapshot.js +7 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +34 -12
- package/dist/lib/scaffold-render-sweep.d.ts +33 -0
- package/dist/lib/scaffold-render-sweep.js +45 -0
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +122 -538
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/surfaces.js +10 -2
- package/dist/sync-routes.d.ts +30 -4
- package/dist/sync-routes.js +271 -18
- package/dist/template-record.d.ts +14 -4
- package/dist/template-record.js +23 -5
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +31 -19
- package/dist/update.d.ts +10 -1
- package/dist/update.js +540 -16
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +9 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +15 -3
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +9 -2
- package/templates/apps/api/CLAUDE.md +122 -82
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -4
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1857
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +77 -24
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -12
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +21 -5
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +14 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +28 -7
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +2 -2
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +7 -0
- package/templates/apps/web/lib/auth-email.ts +1 -1
- package/templates/apps/web/lib/magnet-routes.ts +3 -1
- package/templates/apps/web/lib/newsletter.actions.ts +9 -6
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +32 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +757 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +48 -35
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -292
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -254
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -318
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -264
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -1815
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -325
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1300
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -268
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -95
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -129
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -573
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -480
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -465
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -448
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -808
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2026
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -528
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -867
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1176
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -444
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -154
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -313
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -755
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -295
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -868
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -396
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -681
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -308
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -385
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1003
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1188
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2073
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1151
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -65
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -187
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -165
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -228
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -135
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -474
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -225
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -256
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -307
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -223
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -342
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -526
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -142
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -139
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -64
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -137
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -74
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -75
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -59
- package/templates/apps/mcp/src/tools/search_content.ts +0 -67
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -57
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -961
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -240
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,1815 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAPI 3.1 spec for the public content API.
|
|
3
|
-
*
|
|
4
|
-
* Hand-authored rather than generated from Zod schemas. Three endpoints
|
|
5
|
-
* with stable shapes is small enough that a literal spec is faster to read
|
|
6
|
-
* and review than a generated one.
|
|
7
|
-
*
|
|
8
|
-
* When this surface grows beyond a handful of routes, swap in a generator
|
|
9
|
-
* and delete this file; the route handlers don't depend on the spec shape.
|
|
10
|
-
*
|
|
11
|
-
* Hand-authored does NOT mean self-described: this file is byte-synced into
|
|
12
|
-
* every consuming site, so the site's own name and the deployment's own API
|
|
13
|
-
* origin are resolved at serve time (#5306, #5307) rather than written in as
|
|
14
|
-
* literals. Everything a literal can correctly say about every deployment —
|
|
15
|
-
* route shapes, schemas — stays a literal.
|
|
16
|
-
*/
|
|
17
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
|
|
20
|
-
import { SCHEMA_ORG_TYPES } from "@working-theory/content-adapters";
|
|
21
|
-
import { logger } from "@working-theory/observability";
|
|
22
|
-
import { SITE_CATEGORIES } from "@working-theory/schema/categories";
|
|
23
|
-
import { parse as parseYaml } from "yaml";
|
|
24
|
-
|
|
25
|
-
import { config } from "../config/index.js";
|
|
26
|
-
import { isModuleEnabled } from "../config/modules.js";
|
|
27
|
-
import { getContentRoot } from "../lib/content-adapter.js";
|
|
28
|
-
|
|
29
|
-
import { toOpenApiPath, type RegisteredRoute } from "./surface-drift.js";
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The version of the PUBLIC API CONTRACT — the `/v1` route prefix this spec
|
|
33
|
-
* documents — and deliberately not the version of any npm package (#5307
|
|
34
|
-
* AC-4). The `@working-theory/*` packages move on their own 0.x line and are
|
|
35
|
-
* released per-change; the contract an agent codes against moves only when a
|
|
36
|
-
* route's shape does. Syncing this to a package version would make every
|
|
37
|
-
* package release look like an API change to a caller that pins on it.
|
|
38
|
-
*
|
|
39
|
-
* Bump the major only alongside a new `/vN` prefix.
|
|
40
|
-
*/
|
|
41
|
-
export const API_VERSION = "1.0.0";
|
|
42
|
-
|
|
43
|
-
/** Name used when the site's own identity cannot be resolved (#5307 AC-3). */
|
|
44
|
-
const NEUTRAL_DISPLAY_NAME = "Content API";
|
|
45
|
-
|
|
46
|
-
/** Spec title used when the site's own name cannot be resolved (#5307 AC-3). */
|
|
47
|
-
const NEUTRAL_SPEC_TITLE = "Public Content API";
|
|
48
|
-
|
|
49
|
-
/** Root-index description used when `_site.mdx` carries none. */
|
|
50
|
-
const NEUTRAL_DESCRIPTION = "Local auth + session backend + public content API";
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* How this deployment describes itself — resolved from the site's own config
|
|
54
|
-
* and this deployment's own env, never from a literal naming the kit.
|
|
55
|
-
*/
|
|
56
|
-
export interface ApiIdentity {
|
|
57
|
-
/** `siteName` from `_site.mdx`. Absent when the file is missing/unreadable. */
|
|
58
|
-
siteName?: string;
|
|
59
|
-
/** `siteIdentity.description` from `_site.mdx`, when present. */
|
|
60
|
-
siteDescription?: string;
|
|
61
|
-
/**
|
|
62
|
-
* The public origin this API is reachable at, from `AILK_PUBLIC_API_URL`.
|
|
63
|
-
*
|
|
64
|
-
* NOT `_site.mdx`'s `baseUrl` (#5306 AC-3). `baseUrl` is the SITE origin;
|
|
65
|
-
* the API is deployed on a different host — `samhenry.org` serves the site
|
|
66
|
-
* and `api.samhenry.org` serves this API. Pointing `servers` at `baseUrl`
|
|
67
|
-
* would look right in review, pass every test that does not deploy, and
|
|
68
|
-
* send agents to the site's web origin instead of its API. The two are
|
|
69
|
-
* separate facts and only the deployment knows the second one.
|
|
70
|
-
*/
|
|
71
|
-
publicApiUrl?: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let cachedIdentity: ApiIdentity | undefined;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Resolve this deployment's identity, reading `_site.mdx` once per process.
|
|
78
|
-
*
|
|
79
|
-
* Deliberately synchronous, and deliberately a local frontmatter read rather
|
|
80
|
-
* than `@working-theory/site-config`'s async `loadSiteConfig`: the spec is
|
|
81
|
-
* built inside a sync route handler, and only two scalar fields are needed.
|
|
82
|
-
* This mirrors `lib/locales.ts`, which reads `project.yaml` the same way and
|
|
83
|
-
* for the same boundary reason.
|
|
84
|
-
*
|
|
85
|
-
* Every failure path degrades to the neutral default (#5307 AC-3) — a missing
|
|
86
|
-
* or malformed `_site.mdx` must never take the API down, and must never fall
|
|
87
|
-
* back to the kit's name.
|
|
88
|
-
*/
|
|
89
|
-
export function resolveApiIdentity(): ApiIdentity {
|
|
90
|
-
if (!cachedIdentity) cachedIdentity = readApiIdentity();
|
|
91
|
-
return cachedIdentity;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Test/utility helper — drop the cached identity so the next call re-reads. */
|
|
95
|
-
export function resetApiIdentity(): void {
|
|
96
|
-
cachedIdentity = undefined;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function readApiIdentity(): ApiIdentity {
|
|
100
|
-
const identity: ApiIdentity = {};
|
|
101
|
-
|
|
102
|
-
const publicApiUrl = normaliseOrigin(config.AILK_PUBLIC_API_URL);
|
|
103
|
-
if (publicApiUrl) identity.publicApiUrl = publicApiUrl;
|
|
104
|
-
|
|
105
|
-
const frontmatter = readSiteFrontmatter();
|
|
106
|
-
const siteName = frontmatter?.["siteName"];
|
|
107
|
-
if (typeof siteName === "string" && siteName.trim()) {
|
|
108
|
-
identity.siteName = siteName.trim();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const siteIdentity = frontmatter?.["siteIdentity"];
|
|
112
|
-
const description =
|
|
113
|
-
siteIdentity && typeof siteIdentity === "object"
|
|
114
|
-
? (siteIdentity as Record<string, unknown>)["description"]
|
|
115
|
-
: undefined;
|
|
116
|
-
if (typeof description === "string" && description.trim()) {
|
|
117
|
-
identity.siteDescription = description.trim();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!identity.siteName) {
|
|
121
|
-
logger.warn(
|
|
122
|
-
{ neutralName: NEUTRAL_DISPLAY_NAME },
|
|
123
|
-
"No readable siteName in _site.mdx — the API will describe itself with a neutral default",
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return identity;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Read and parse `_site.mdx` frontmatter, or `undefined` on any failure. */
|
|
131
|
-
function readSiteFrontmatter(): Record<string, unknown> | undefined {
|
|
132
|
-
const path = join(getContentRoot(), "_site.mdx");
|
|
133
|
-
if (!existsSync(path)) return undefined;
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const raw = readFileSync(path, "utf8");
|
|
137
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
138
|
-
if (!match?.[1]) return undefined;
|
|
139
|
-
|
|
140
|
-
const parsed: unknown = parseYaml(match[1]);
|
|
141
|
-
return parsed && typeof parsed === "object"
|
|
142
|
-
? (parsed as Record<string, unknown>)
|
|
143
|
-
: undefined;
|
|
144
|
-
} catch {
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Accept an absolute http(s) origin and strip a trailing slash. Anything else
|
|
151
|
-
* resolves to `undefined` so the caller degrades to a document-relative
|
|
152
|
-
* server rather than publishing a malformed absolute URL (#5306 AC-2).
|
|
153
|
-
*/
|
|
154
|
-
function normaliseOrigin(value: string | undefined): string | undefined {
|
|
155
|
-
const trimmed = value?.trim();
|
|
156
|
-
if (!trimmed) return undefined;
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const url = new URL(trimmed);
|
|
160
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
161
|
-
logger.warn(
|
|
162
|
-
{ AILK_PUBLIC_API_URL: trimmed },
|
|
163
|
-
"AILK_PUBLIC_API_URL is not an http(s) URL — the OpenAPI servers block will fall back to a document-relative origin",
|
|
164
|
-
);
|
|
165
|
-
return undefined;
|
|
166
|
-
}
|
|
167
|
-
return trimmed.replace(/\/+$/, "");
|
|
168
|
-
} catch {
|
|
169
|
-
logger.warn(
|
|
170
|
-
{ AILK_PUBLIC_API_URL: trimmed },
|
|
171
|
-
"AILK_PUBLIC_API_URL is not a parseable URL — the OpenAPI servers block will fall back to a document-relative origin",
|
|
172
|
-
);
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** The name this API introduces itself by on the root index. */
|
|
178
|
-
export function apiDisplayName(identity: ApiIdentity): string {
|
|
179
|
-
return identity.siteName ? `${identity.siteName} API` : NEUTRAL_DISPLAY_NAME;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** The description this API introduces itself by on the root index. */
|
|
183
|
-
export function apiDescription(identity: ApiIdentity): string {
|
|
184
|
-
return identity.siteDescription ?? NEUTRAL_DESCRIPTION;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Resolve the `servers` block.
|
|
189
|
-
*
|
|
190
|
-
* With an origin configured, one entry naming this deployment. Without one,
|
|
191
|
-
* a single document-relative entry: per OpenAPI 3.1 a `/` server resolves
|
|
192
|
-
* against the origin the document was served from, so an agent that fetched
|
|
193
|
-
* this spec from `https://api.example.com/v1/openapi.json` still reaches the
|
|
194
|
-
* right host. That makes absence a correct-by-construction degradation rather
|
|
195
|
-
* than a silently wrong URL (#5306 AC-2), and the entry's own description
|
|
196
|
-
* names the unset variable so the reason travels with the served document.
|
|
197
|
-
*/
|
|
198
|
-
function resolveServers(
|
|
199
|
-
publicApiUrl: string | undefined,
|
|
200
|
-
): Array<{ url: string; description: string }> {
|
|
201
|
-
const origin = normaliseOrigin(publicApiUrl);
|
|
202
|
-
if (origin) {
|
|
203
|
-
return [{ url: origin, description: "This deployment" }];
|
|
204
|
-
}
|
|
205
|
-
return [
|
|
206
|
-
{
|
|
207
|
-
url: "/",
|
|
208
|
-
description:
|
|
209
|
-
"This deployment — relative to the origin that served this document " +
|
|
210
|
-
"(AILK_PUBLIC_API_URL is unset)",
|
|
211
|
-
},
|
|
212
|
-
];
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* The route table a built server registered (#5314).
|
|
217
|
-
*
|
|
218
|
-
* Carried on the Fastify instance (`server.decorate`) and threaded into the
|
|
219
|
-
* two surfaces as an argument — deliberately NOT module-level state. A
|
|
220
|
-
* module-level array is shared by every server built in one process, and
|
|
221
|
-
* `apps/api/src/vercel-handler.ts`'s `getServer()` is a check-then-set: it
|
|
222
|
-
* tests `!serverInstance`, then awaits twice before assigning, so two
|
|
223
|
-
* concurrent invocations can both call `build()`. With shared state, one
|
|
224
|
-
* build's reset would land mid-fill of the other and leave a truncated or
|
|
225
|
-
* doubled table — the surfaces would then silently misreport, which is the
|
|
226
|
-
* exact failure this card exists to remove. Per-instance state makes that
|
|
227
|
-
* unreachable whether or not the race fires.
|
|
228
|
-
*
|
|
229
|
-
* Registration is CONDITIONAL — `isModuleEnabled()` gates paid-module routes
|
|
230
|
-
* and `shouldMountTestAuth()` gates the test-auth route — so this is what THIS
|
|
231
|
-
* deployment serves, not a catalogue of what the build could serve (spec D1).
|
|
232
|
-
* A route absent because its module is off is absent from both surfaces too,
|
|
233
|
-
* which is why a disabled slice never reads as drift.
|
|
234
|
-
*/
|
|
235
|
-
|
|
236
|
-
/** The decorator key both surfaces read the route table from. */
|
|
237
|
-
export const REGISTERED_ROUTES_DECORATOR = "registeredRoutes";
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Read the route table off a Fastify instance (or anything carrying the
|
|
241
|
-
* decorator), returning `[]` when it is absent.
|
|
242
|
-
*
|
|
243
|
-
* Empty is a supported state, not a bug: `buildOpenApiSpec()` is called
|
|
244
|
-
* directly by unit tests with no server in play, and this file is byte-synced
|
|
245
|
-
* into every consuming site. `paths` degrades to its hand-authored entries
|
|
246
|
-
* rather than publishing an empty document.
|
|
247
|
-
*/
|
|
248
|
-
export function readRegisteredRoutes(source: unknown): RegisteredRoute[] {
|
|
249
|
-
const routes = (source as Record<string, unknown> | null | undefined)?.[
|
|
250
|
-
REGISTERED_ROUTES_DECORATOR
|
|
251
|
-
];
|
|
252
|
-
return Array.isArray(routes) ? (routes as RegisteredRoute[]) : [];
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* The `{ ok: false, error: { code, message } }` envelope (apps/api/CLAUDE.md)
|
|
257
|
-
* identity-bound tenant routes answer on 400/401/403 — distinct from the
|
|
258
|
-
* `{ error: string }` shape `ErrorResponse` documents for the anonymous
|
|
259
|
-
* content routes. Shared here so `/v1/schedule/appointments`'s three error
|
|
260
|
-
* responses (#6085) reference one literal rather than three copies.
|
|
261
|
-
*/
|
|
262
|
-
const tenantErrorResponseSchema = {
|
|
263
|
-
type: "object",
|
|
264
|
-
required: ["ok", "error"],
|
|
265
|
-
properties: {
|
|
266
|
-
ok: { type: "boolean", enum: [false] },
|
|
267
|
-
error: {
|
|
268
|
-
type: "object",
|
|
269
|
-
required: ["code", "message"],
|
|
270
|
-
properties: {
|
|
271
|
-
code: { type: "string" },
|
|
272
|
-
message: { type: "string" },
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
} as const;
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Root-index keys this API has already published, by Fastify path (#5314 D6).
|
|
280
|
-
*
|
|
281
|
-
* Deriving the root index without this overlay would rename all eleven keys a
|
|
282
|
-
* consumer may already read, which ADR-0020 counts as a breaking change and
|
|
283
|
-
* would owe a `/v2`. With it, every published key keeps its name and only new
|
|
284
|
-
* keys appear, so the change is purely additive.
|
|
285
|
-
*
|
|
286
|
-
* `billing` and `billingPortalSession` become CONDITIONAL rather than
|
|
287
|
-
* disappearing: they are emitted exactly when their routes register, which is
|
|
288
|
-
* the fix for a root index that advertised both on a build where neither
|
|
289
|
-
* registers and both 404.
|
|
290
|
-
*/
|
|
291
|
-
const CURATED_ROOT_INDEX_KEYS: Record<string, string> = {
|
|
292
|
-
"/health": "health",
|
|
293
|
-
"/v1/openapi.json": "openapi",
|
|
294
|
-
"/v1/auth/tokens": "tokens",
|
|
295
|
-
"/v1/user/profile": "profile",
|
|
296
|
-
"/v1/user/data/:key": "userData",
|
|
297
|
-
"/v1/content/types": "contentTypes",
|
|
298
|
-
"/v1/content/search": "contentSearch",
|
|
299
|
-
"/v1/content/:type": "contentList",
|
|
300
|
-
"/v1/content/:type/:slug": "contentPage",
|
|
301
|
-
"/v1/checkout/sessions": "checkoutSessions",
|
|
302
|
-
// #6084 §4.2 — one entry, so the root index publishes a stable
|
|
303
|
-
// `checkoutSession` key rather than a `generatedRootIndexKey` fallback.
|
|
304
|
-
"/v1/checkout/sessions/:id": "checkoutSession",
|
|
305
|
-
"/v1/billing": "billing",
|
|
306
|
-
"/v1/billing/portal-session": "billingPortalSession",
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Canonical method order for a root-index value, so `GET/POST /v1/x` is
|
|
311
|
-
* stable across runs rather than following registration order.
|
|
312
|
-
*/
|
|
313
|
-
const METHOD_ORDER = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
314
|
-
|
|
315
|
-
function byCanonicalMethod(a: string, b: string): number {
|
|
316
|
-
const ai = METHOD_ORDER.indexOf(a);
|
|
317
|
-
const bi = METHOD_ORDER.indexOf(b);
|
|
318
|
-
return (
|
|
319
|
-
(ai === -1 ? METHOD_ORDER.length : ai) -
|
|
320
|
-
(bi === -1 ? METHOD_ORDER.length : bi) || a.localeCompare(b)
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* A deterministic root-index key for a path with no curated name:
|
|
326
|
-
* `/v1/project-listings/public` becomes `projectListingsPublic`, and a
|
|
327
|
-
* `:param` segment contributes its bare name.
|
|
328
|
-
*/
|
|
329
|
-
function generatedRootIndexKey(path: string): string {
|
|
330
|
-
const segments = path
|
|
331
|
-
.replace(/^\/v1\//, "/")
|
|
332
|
-
.split("/")
|
|
333
|
-
.filter(Boolean)
|
|
334
|
-
.map((segment) => segment.replace(/^:/, ""));
|
|
335
|
-
if (segments.length === 0) return "root";
|
|
336
|
-
|
|
337
|
-
return segments
|
|
338
|
-
.map((segment, index) => {
|
|
339
|
-
const camel = segment
|
|
340
|
-
.replace(/[^a-zA-Z0-9]+(.)/g, (_m, c: string) => c.toUpperCase())
|
|
341
|
-
.replace(/[^a-zA-Z0-9]/g, "");
|
|
342
|
-
return index === 0
|
|
343
|
-
? camel.charAt(0).toLowerCase() + camel.slice(1)
|
|
344
|
-
: camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
345
|
-
})
|
|
346
|
-
.join("");
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* The root index `endpoints` object, derived from the routes this process
|
|
351
|
-
* registered (#5314 AC-1).
|
|
352
|
-
*
|
|
353
|
-
* One entry per PATH, valued `"METHOD[/METHOD...] /path"` — the shape this
|
|
354
|
-
* surface has always published (`"GET/PATCH /v1/user/profile"`). The SHAPE is
|
|
355
|
-
* preserved, not every value: `tokens` goes from `"POST/GET/DELETE
|
|
356
|
-
* /v1/auth/tokens"` to `"GET/POST /v1/auth/tokens"`, because `DELETE` was
|
|
357
|
-
* never served at that path (it is `/v1/auth/tokens/:id`, now its own entry).
|
|
358
|
-
* Key NAMES are preserved outright, which is what keeps this additive under
|
|
359
|
-
* ADR-0020; the value corrections are the drift being fixed. Curated keys win; everything else gets a
|
|
360
|
-
* generated one, de-duplicated by suffix so two paths can never collapse onto
|
|
361
|
-
* one key and silently hide a route.
|
|
362
|
-
*
|
|
363
|
-
* An empty route table yields an empty object rather than a stale literal: the
|
|
364
|
-
* only caller is the `GET /` handler on a built server, whose own route is in
|
|
365
|
-
* the table by request time, so an empty result would mean the hook had failed
|
|
366
|
-
* and is better surfaced than papered over.
|
|
367
|
-
*/
|
|
368
|
-
export function deriveRootEndpoints(
|
|
369
|
-
routes: RegisteredRoute[],
|
|
370
|
-
): Record<string, string> {
|
|
371
|
-
const methodsByPath = new Map<string, string[]>();
|
|
372
|
-
for (const { method, url } of routes) {
|
|
373
|
-
const methods = methodsByPath.get(url) ?? [];
|
|
374
|
-
if (!methods.includes(method)) methods.push(method);
|
|
375
|
-
methodsByPath.set(url, methods);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const endpoints: Record<string, string> = {};
|
|
379
|
-
const used = new Set<string>();
|
|
380
|
-
for (const [path, methods] of methodsByPath) {
|
|
381
|
-
const base = CURATED_ROOT_INDEX_KEYS[path] ?? generatedRootIndexKey(path);
|
|
382
|
-
let key = base;
|
|
383
|
-
for (let n = 2; used.has(key); n += 1) key = `${base}${n}`;
|
|
384
|
-
used.add(key);
|
|
385
|
-
endpoints[key] =
|
|
386
|
-
`${[...methods].sort(byCanonicalMethod).join("/")} ${path}`;
|
|
387
|
-
}
|
|
388
|
-
return endpoints;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/** An OpenAPI `operationId` for a route with no hand-authored operation. */
|
|
392
|
-
function derivedOperationId(method: string, path: string): string {
|
|
393
|
-
const suffix = generatedRootIndexKey(path);
|
|
394
|
-
return `${method.toLowerCase()}${suffix.charAt(0).toUpperCase()}${suffix.slice(1)}`;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* The minimal OpenAPI 3.1 operation a route table can honestly produce.
|
|
399
|
-
*
|
|
400
|
-
* No route in this app carries a Fastify `schema` — validation is Zod inside
|
|
401
|
-
* the handler — so the route table knows a route's method and path and
|
|
402
|
-
* nothing else (#5314 D2). Inventing a request or response schema here would
|
|
403
|
-
* be worse than omitting one: an agent codes against what the document says.
|
|
404
|
-
*/
|
|
405
|
-
function derivedOperation(method: string, fastifyPath: string) {
|
|
406
|
-
const parameters = fastifyPath
|
|
407
|
-
.split("/")
|
|
408
|
-
.filter((segment) => segment.startsWith(":"))
|
|
409
|
-
.map((segment) => ({
|
|
410
|
-
name: segment.slice(1),
|
|
411
|
-
in: "path",
|
|
412
|
-
required: true,
|
|
413
|
-
schema: { type: "string" },
|
|
414
|
-
}));
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
operationId: derivedOperationId(method, fastifyPath),
|
|
418
|
-
summary: `${method.toUpperCase()} ${toOpenApiPath(fastifyPath)}`,
|
|
419
|
-
description:
|
|
420
|
-
"Derived from this deployment's route table (#5314). Request and " +
|
|
421
|
-
"response shapes are not described: this route carries no schema the " +
|
|
422
|
-
"route table can read.",
|
|
423
|
-
...(parameters.length > 0 && { parameters }),
|
|
424
|
-
responses: { "200": { description: "OK" } },
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* `paths` = every registered route, overlaid with the hand-authored operations.
|
|
430
|
-
*
|
|
431
|
-
* The derived half makes the endpoint SET complete and correct by construction
|
|
432
|
-
* (#5314 AC-1/AC-4); the overlay keeps every hand-authored schema, example and
|
|
433
|
-
* description exactly as written (#5314 D2). Hand-authored wins per method, and
|
|
434
|
-
* path-level keys on a hand-authored item are preserved.
|
|
435
|
-
*
|
|
436
|
-
* With an EMPTY registry the hand-authored object is returned untouched. That
|
|
437
|
-
* is the byte-sync degradation floor (#5314 FR7): `buildOpenApiSpec()` is
|
|
438
|
-
* called directly by unit tests with no server in play, and a consuming site
|
|
439
|
-
* that somehow builds no route table still serves the seven documented paths
|
|
440
|
-
* rather than an empty document.
|
|
441
|
-
*/
|
|
442
|
-
function mergeDerivedPaths(
|
|
443
|
-
routes: RegisteredRoute[],
|
|
444
|
-
handAuthored: Record<string, Record<string, unknown>>,
|
|
445
|
-
): Record<string, Record<string, unknown>> {
|
|
446
|
-
if (routes.length === 0) return handAuthored;
|
|
447
|
-
|
|
448
|
-
const paths: Record<string, Record<string, unknown>> = {};
|
|
449
|
-
for (const { method, url } of routes) {
|
|
450
|
-
const path = toOpenApiPath(url);
|
|
451
|
-
paths[path] = {
|
|
452
|
-
...paths[path],
|
|
453
|
-
[method.toLowerCase()]: derivedOperation(method, url),
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
for (const [path, item] of Object.entries(handAuthored)) {
|
|
458
|
-
paths[path] = { ...paths[path], ...item };
|
|
459
|
-
}
|
|
460
|
-
return paths;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export function buildOpenApiSpec(
|
|
464
|
-
identity: ApiIdentity = resolveApiIdentity(),
|
|
465
|
-
registeredRoutes: RegisteredRoute[] = [],
|
|
466
|
-
) {
|
|
467
|
-
const typeEnum = [...SCHEMA_ORG_TYPES];
|
|
468
|
-
// Derived, never hand-listed: widening the registry widens the documented
|
|
469
|
-
// enum with no second edit here.
|
|
470
|
-
const categoryEnum = SITE_CATEGORIES.map((category) => category.slug);
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
openapi: "3.1.0",
|
|
474
|
-
info: {
|
|
475
|
-
title: identity.siteName
|
|
476
|
-
? `${identity.siteName} — ${NEUTRAL_SPEC_TITLE}`
|
|
477
|
-
: NEUTRAL_SPEC_TITLE,
|
|
478
|
-
version: API_VERSION,
|
|
479
|
-
description:
|
|
480
|
-
"HTTP API exposing this site's content as schema.org JSON-LD, plus a " +
|
|
481
|
-
"small set of anonymous write routes (lead capture, checkout). Designed " +
|
|
482
|
-
"for agent consumption (AX) — every content response carries `@context` " +
|
|
483
|
-
"and `@type` at the document root.",
|
|
484
|
-
// No `license`: this file is byte-synced into every consuming site, so a
|
|
485
|
-
// literal here would assert a licence over a deployment that never chose
|
|
486
|
-
// one (#5307 AC-2). OpenAPI 3.1 makes `info.license` optional; a site
|
|
487
|
-
// that wants to publish one declares it where its own licence lives.
|
|
488
|
-
},
|
|
489
|
-
servers: resolveServers(identity.publicApiUrl),
|
|
490
|
-
paths: mergeDerivedPaths(registeredRoutes, {
|
|
491
|
-
// Module-conditional (commerce, D-47 / #5364) — mirroring the CONDITIONAL
|
|
492
|
-
// treatment `CURATED_ROOT_INDEX_KEYS`'s doc comment above already
|
|
493
|
-
// describes for `billing`/`billingPortalSession`. Those two have no
|
|
494
|
-
// hand-authored `paths` entry here at all (mergeDerivedPaths's DERIVED
|
|
495
|
-
// half is what publishes them, so they only ever appear when actually
|
|
496
|
-
// registered); `/v1/checkout/sessions` is the one hand-authored entry
|
|
497
|
-
// in this object, and mergeDerivedPaths always merges hand-authored
|
|
498
|
-
// paths in regardless of registration — so without this gate, a build
|
|
499
|
-
// with commerce off still advertises the operation while `registeredRoutes`
|
|
500
|
-
// has no matching entry, which is exactly the orphaned-surface drift
|
|
501
|
-
// surface-drift.test.ts exists to catch (#5314).
|
|
502
|
-
...(isModuleEnabled("commerce") && {
|
|
503
|
-
"/v1/checkout/sessions": {
|
|
504
|
-
post: {
|
|
505
|
-
operationId: "createCheckoutSession",
|
|
506
|
-
summary: "Create a Stripe hosted-page checkout session",
|
|
507
|
-
description:
|
|
508
|
-
"Resolves a productCode to a Stripe price via lookup_key and creates a " +
|
|
509
|
-
"hosted-page checkout session. Returns the session ID and redirect URL. " +
|
|
510
|
-
"Sandbox-only: live Stripe keys are rejected at runtime.",
|
|
511
|
-
requestBody: {
|
|
512
|
-
required: true,
|
|
513
|
-
content: {
|
|
514
|
-
"application/json": {
|
|
515
|
-
schema: {
|
|
516
|
-
$ref: "#/components/schemas/CheckoutSessionRequest",
|
|
517
|
-
},
|
|
518
|
-
},
|
|
519
|
-
},
|
|
520
|
-
},
|
|
521
|
-
responses: {
|
|
522
|
-
"201": {
|
|
523
|
-
description: "Checkout session created",
|
|
524
|
-
content: {
|
|
525
|
-
"application/json": {
|
|
526
|
-
schema: {
|
|
527
|
-
$ref: "#/components/schemas/CheckoutSessionResponse",
|
|
528
|
-
},
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
"400": {
|
|
533
|
-
description: "Validation error — invalid request body",
|
|
534
|
-
content: {
|
|
535
|
-
"application/json": {
|
|
536
|
-
schema: {
|
|
537
|
-
$ref: "#/components/schemas/ValidationErrorResponse",
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
},
|
|
541
|
-
},
|
|
542
|
-
"404": {
|
|
543
|
-
description:
|
|
544
|
-
"Unknown product code — no active Stripe price for the given productCode",
|
|
545
|
-
content: {
|
|
546
|
-
"application/json": {
|
|
547
|
-
schema: {
|
|
548
|
-
$ref: "#/components/schemas/UnknownProductCodeResponse",
|
|
549
|
-
},
|
|
550
|
-
},
|
|
551
|
-
},
|
|
552
|
-
},
|
|
553
|
-
"500": {
|
|
554
|
-
description:
|
|
555
|
-
"Live-mode forbidden — STRIPE_SECRET_KEY is a live key",
|
|
556
|
-
content: {
|
|
557
|
-
"application/json": {
|
|
558
|
-
schema: {
|
|
559
|
-
$ref: "#/components/schemas/LiveModeForbiddenResponse",
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
"502": {
|
|
565
|
-
description:
|
|
566
|
-
"Checkout unavailable — Stripe returned an unexpected error",
|
|
567
|
-
content: {
|
|
568
|
-
"application/json": {
|
|
569
|
-
schema: {
|
|
570
|
-
$ref: "#/components/schemas/CheckoutUnavailableResponse",
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
},
|
|
578
|
-
// #6084 — the read half, under the SAME commerce gate as the POST
|
|
579
|
-
// above (AC-3). PII-free by construction: the response schema names
|
|
580
|
-
// the D3 six-key allow-list, never a spread.
|
|
581
|
-
"/v1/checkout/sessions/{id}": {
|
|
582
|
-
get: {
|
|
583
|
-
operationId: "getCheckoutSession",
|
|
584
|
-
summary: "Read a Checkout Session's payment status and paid line items",
|
|
585
|
-
description:
|
|
586
|
-
"Reads a Checkout Session's status, payment status, and paid line " +
|
|
587
|
-
"items. Read-only — never creates or mutates a Stripe object. The " +
|
|
588
|
-
"response is PII-free: no customer identity, payment method, or " +
|
|
589
|
-
"metadata field is ever returned.",
|
|
590
|
-
parameters: [
|
|
591
|
-
{
|
|
592
|
-
name: "id",
|
|
593
|
-
in: "path",
|
|
594
|
-
required: true,
|
|
595
|
-
description: "Stripe Checkout Session id (cs_…)",
|
|
596
|
-
schema: { type: "string", pattern: "^cs_[A-Za-z0-9_]{1,250}$" },
|
|
597
|
-
},
|
|
598
|
-
],
|
|
599
|
-
responses: {
|
|
600
|
-
"200": {
|
|
601
|
-
description: "Checkout Session status, payment status, and paid line items",
|
|
602
|
-
content: {
|
|
603
|
-
"application/json": {
|
|
604
|
-
schema: {
|
|
605
|
-
$ref: "#/components/schemas/CheckoutSessionReadResponse",
|
|
606
|
-
},
|
|
607
|
-
},
|
|
608
|
-
},
|
|
609
|
-
},
|
|
610
|
-
"400": {
|
|
611
|
-
description: "Validation error — the id fails the shape check",
|
|
612
|
-
content: {
|
|
613
|
-
"application/json": {
|
|
614
|
-
schema: {
|
|
615
|
-
$ref: "#/components/schemas/ValidationErrorResponse",
|
|
616
|
-
},
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
"404": {
|
|
621
|
-
description: "Unknown session — no Checkout Session matches the given id",
|
|
622
|
-
content: {
|
|
623
|
-
"application/json": {
|
|
624
|
-
schema: {
|
|
625
|
-
$ref: "#/components/schemas/UnknownSessionResponse",
|
|
626
|
-
},
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
|
-
},
|
|
630
|
-
"500": {
|
|
631
|
-
description:
|
|
632
|
-
"Live-mode forbidden — STRIPE_SECRET_KEY is a live key",
|
|
633
|
-
content: {
|
|
634
|
-
"application/json": {
|
|
635
|
-
schema: {
|
|
636
|
-
$ref: "#/components/schemas/LiveModeForbiddenResponse",
|
|
637
|
-
},
|
|
638
|
-
},
|
|
639
|
-
},
|
|
640
|
-
},
|
|
641
|
-
"502": {
|
|
642
|
-
description:
|
|
643
|
-
"Checkout unavailable — Stripe returned an unexpected error",
|
|
644
|
-
content: {
|
|
645
|
-
"application/json": {
|
|
646
|
-
schema: {
|
|
647
|
-
$ref: "#/components/schemas/CheckoutUnavailableResponse",
|
|
648
|
-
},
|
|
649
|
-
},
|
|
650
|
-
},
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
},
|
|
654
|
-
},
|
|
655
|
-
}),
|
|
656
|
-
// Module-conditional (scheduling, #6085 brief H7) — mirrors the
|
|
657
|
-
// `/v1/checkout/sessions` gate immediately above: without it a build
|
|
658
|
-
// with the module off would still advertise the operation while
|
|
659
|
-
// `registeredRoutes` carries no matching entry (surface-drift.test.ts,
|
|
660
|
-
// #5314). The doc comment below deliberately never names the
|
|
661
|
-
// scheduling provider (§ 1.12, § 9 SEC-13) — this file is scanned by
|
|
662
|
-
// `architecture.yaml`'s `cal\.com` forbidden pattern like every other
|
|
663
|
-
// `apps/api/**` file, which is EXCLUDED from it, but the wording
|
|
664
|
-
// itself is written provider-neutral anyway since it documents the
|
|
665
|
-
// AILK-side contract, not the provider's.
|
|
666
|
-
...(isModuleEnabled("scheduling") && {
|
|
667
|
-
"/v1/schedule/appointments": {
|
|
668
|
-
get: {
|
|
669
|
-
operationId: "listAppointments",
|
|
670
|
-
summary: "List this site's own appointments",
|
|
671
|
-
description:
|
|
672
|
-
"The tenant-guarded read over this site's normalized booking " +
|
|
673
|
-
"rows (#6085). Identity-bound: requires an authenticated " +
|
|
674
|
-
"session carrying an owner|admin role on the active workspace " +
|
|
675
|
-
"— the rows carry attendee contact PII. `window` selects " +
|
|
676
|
-
"upcoming (the default), past, or all; `cursor` is the " +
|
|
677
|
-
"previous page's last row id.",
|
|
678
|
-
parameters: [
|
|
679
|
-
{
|
|
680
|
-
name: "limit",
|
|
681
|
-
in: "query",
|
|
682
|
-
required: false,
|
|
683
|
-
schema: { type: "integer", minimum: 1, maximum: 200, default: 50 },
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
name: "cursor",
|
|
687
|
-
in: "query",
|
|
688
|
-
required: false,
|
|
689
|
-
description: "The previous page's last row id.",
|
|
690
|
-
schema: { type: "string" },
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
name: "window",
|
|
694
|
-
in: "query",
|
|
695
|
-
required: false,
|
|
696
|
-
schema: {
|
|
697
|
-
type: "string",
|
|
698
|
-
enum: ["upcoming", "past", "all"],
|
|
699
|
-
default: "upcoming",
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
],
|
|
703
|
-
responses: {
|
|
704
|
-
"200": {
|
|
705
|
-
description: "OK",
|
|
706
|
-
content: {
|
|
707
|
-
"application/json": {
|
|
708
|
-
schema: { $ref: "#/components/schemas/AppointmentListResponse" },
|
|
709
|
-
},
|
|
710
|
-
},
|
|
711
|
-
},
|
|
712
|
-
// The `{ ok: false, error: { code, message } }` shape
|
|
713
|
-
// (apps/api/CLAUDE.md) — distinct from the `{ error }` string
|
|
714
|
-
// shape `ErrorResponse` documents for the anonymous content
|
|
715
|
-
// routes, so 400/401/403 each get this inline schema rather
|
|
716
|
-
// than `$ref`ing that one.
|
|
717
|
-
"400": {
|
|
718
|
-
description: "Invalid query parameters, or an unresolvable cursor",
|
|
719
|
-
content: {
|
|
720
|
-
"application/json": { schema: tenantErrorResponseSchema },
|
|
721
|
-
},
|
|
722
|
-
},
|
|
723
|
-
"401": {
|
|
724
|
-
description: "Authentication required",
|
|
725
|
-
content: {
|
|
726
|
-
"application/json": { schema: tenantErrorResponseSchema },
|
|
727
|
-
},
|
|
728
|
-
},
|
|
729
|
-
"403": {
|
|
730
|
-
description: "Forbidden — caller lacks the owner|admin role floor",
|
|
731
|
-
content: {
|
|
732
|
-
"application/json": { schema: tenantErrorResponseSchema },
|
|
733
|
-
},
|
|
734
|
-
},
|
|
735
|
-
"429": { $ref: "#/components/responses/RateLimited" },
|
|
736
|
-
"500": { $ref: "#/components/responses/ServerError" },
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
},
|
|
740
|
-
}),
|
|
741
|
-
"/v1/leads/submit": {
|
|
742
|
-
post: {
|
|
743
|
-
operationId: "submitLead",
|
|
744
|
-
summary: "Capture a contact-form lead",
|
|
745
|
-
description:
|
|
746
|
-
"Stores a contact-form-style lead — an email plus an arbitrary payload of " +
|
|
747
|
-
"form fields — for the given siteId. `source` is a free-form tag the caller " +
|
|
748
|
-
"sets to identify the channel (for example `agent`); it is stored as-is and " +
|
|
749
|
-
"not checked against a fixed vocabulary. A 201 response means the lead " +
|
|
750
|
-
"landed — in the database, a local file fallback (outside production " +
|
|
751
|
-
"only), or, on a deployment with no database, the site operator's inbox " +
|
|
752
|
-
"by email (`persisted: \"email\"`); with a database, the response's " +
|
|
753
|
-
"`email` field reports, best-effort, whether the operator was notified. " +
|
|
754
|
-
"A lead is never reported captured when nothing stored or delivered it. " +
|
|
755
|
-
"An optional `Idempotency-Key` header makes a retry safe: with a " +
|
|
756
|
-
"database, a repeat of a stored key in the same workspace with the same " +
|
|
757
|
-
"`email`, `source` and `payload` writes nothing and answers the first " +
|
|
758
|
-
"lead's id with a 201; with anything different it is a 422. Keys are " +
|
|
759
|
-
"scoped per workspace, are first-write-wins, and should be unguessable " +
|
|
760
|
-
"(a UUID, a Stripe Checkout Session id). Without a database there is no " +
|
|
761
|
-
"stored lead to match, so the key has no effect. A consent-bearing lead — " +
|
|
762
|
-
"`source` `contact-form`, `conversational-intake`, `flow:contact` or " +
|
|
763
|
-
"`flow:job-application` — must carry `consent: true` (the visitor " +
|
|
764
|
-
"accepted the site's terms), " +
|
|
765
|
-
"else it is a 400 `consent_required`; the server then records " +
|
|
766
|
-
"`payload.consent` (`statement`, `acceptedAt`) itself, replacing any the " +
|
|
767
|
-
"caller sent, and leaves it out of the Idempotency-Key replay " +
|
|
768
|
-
"comparison. Other sources do not read `consent`.",
|
|
769
|
-
parameters: [
|
|
770
|
-
{
|
|
771
|
-
name: "Idempotency-Key",
|
|
772
|
-
in: "header",
|
|
773
|
-
required: false,
|
|
774
|
-
description:
|
|
775
|
-
"An unguessable value identifying this lead write, so a retry " +
|
|
776
|
-
"answers the first lead instead of writing a second (#7869).",
|
|
777
|
-
schema: { type: "string", pattern: "^[\\x21-\\x7E]{16,255}$" },
|
|
778
|
-
},
|
|
779
|
-
],
|
|
780
|
-
requestBody: {
|
|
781
|
-
required: true,
|
|
782
|
-
content: {
|
|
783
|
-
"application/json": {
|
|
784
|
-
schema: { $ref: "#/components/schemas/LeadSubmitRequest" },
|
|
785
|
-
},
|
|
786
|
-
},
|
|
787
|
-
},
|
|
788
|
-
responses: {
|
|
789
|
-
"201": {
|
|
790
|
-
description: "Lead captured",
|
|
791
|
-
content: {
|
|
792
|
-
"application/json": {
|
|
793
|
-
schema: { $ref: "#/components/schemas/LeadWriteResponse" },
|
|
794
|
-
},
|
|
795
|
-
},
|
|
796
|
-
},
|
|
797
|
-
"400": {
|
|
798
|
-
description:
|
|
799
|
-
"Validation error — invalid request body, or a malformed " +
|
|
800
|
-
"Idempotency-Key; or `consent_required`, a consent-bearing lead " +
|
|
801
|
-
"(a contact lead or a job application) without `consent: true`",
|
|
802
|
-
content: {
|
|
803
|
-
"application/json": {
|
|
804
|
-
schema: {
|
|
805
|
-
oneOf: [
|
|
806
|
-
{ $ref: "#/components/schemas/InvalidInputResponse" },
|
|
807
|
-
{ $ref: "#/components/schemas/ConsentRequiredResponse" },
|
|
808
|
-
],
|
|
809
|
-
},
|
|
810
|
-
},
|
|
811
|
-
},
|
|
812
|
-
},
|
|
813
|
-
"422": {
|
|
814
|
-
description:
|
|
815
|
-
"The Idempotency-Key was already used in this workspace for a lead " +
|
|
816
|
-
"with a different email, source or payload; nothing was written",
|
|
817
|
-
content: {
|
|
818
|
-
"application/json": {
|
|
819
|
-
schema: {
|
|
820
|
-
type: "object",
|
|
821
|
-
required: ["error", "message"],
|
|
822
|
-
properties: {
|
|
823
|
-
error: { type: "string", enum: ["idempotency_key_reused"] },
|
|
824
|
-
message: { type: "string" },
|
|
825
|
-
},
|
|
826
|
-
},
|
|
827
|
-
},
|
|
828
|
-
},
|
|
829
|
-
},
|
|
830
|
-
"503": {
|
|
831
|
-
description:
|
|
832
|
-
"Lead capture unavailable — persistence failed, delivery failed, or " +
|
|
833
|
-
"this deployment has nowhere to put the lead",
|
|
834
|
-
content: {
|
|
835
|
-
"application/json": {
|
|
836
|
-
schema: {
|
|
837
|
-
oneOf: [
|
|
838
|
-
{ $ref: "#/components/schemas/PersistFailedResponse" },
|
|
839
|
-
{
|
|
840
|
-
$ref: "#/components/schemas/LeadCaptureNotConfiguredResponse",
|
|
841
|
-
},
|
|
842
|
-
{ $ref: "#/components/schemas/DeliveryFailedResponse" },
|
|
843
|
-
],
|
|
844
|
-
},
|
|
845
|
-
},
|
|
846
|
-
},
|
|
847
|
-
},
|
|
848
|
-
},
|
|
849
|
-
},
|
|
850
|
-
},
|
|
851
|
-
"/v1/leads/capture": {
|
|
852
|
-
get: {
|
|
853
|
-
operationId: "getLeadCaptureStatus",
|
|
854
|
-
summary: "Whether this site accepts a contact message or a signup",
|
|
855
|
-
description:
|
|
856
|
-
"Two booleans: `contact` — a `POST /v1/leads/submit` would be accepted; " +
|
|
857
|
-
"`newsletter` — a `POST /v1/leads/subscribe` would be. A web page reads " +
|
|
858
|
-
"it to show the contact form or the site's contact email. It never says " +
|
|
859
|
-
"how a lead is delivered. No MCP twin (recorded parity exception): the " +
|
|
860
|
-
"three lead-write tools already answer the same question with a 503.",
|
|
861
|
-
responses: {
|
|
862
|
-
"200": {
|
|
863
|
-
description: "Lead-capture status",
|
|
864
|
-
content: {
|
|
865
|
-
"application/json": {
|
|
866
|
-
schema: {
|
|
867
|
-
type: "object",
|
|
868
|
-
required: ["ok", "data"],
|
|
869
|
-
properties: {
|
|
870
|
-
ok: { type: "boolean", enum: [true] },
|
|
871
|
-
data: { $ref: "#/components/schemas/LeadCaptureStatus" },
|
|
872
|
-
},
|
|
873
|
-
},
|
|
874
|
-
},
|
|
875
|
-
},
|
|
876
|
-
},
|
|
877
|
-
"429": { $ref: "#/components/responses/RateLimited" },
|
|
878
|
-
},
|
|
879
|
-
},
|
|
880
|
-
},
|
|
881
|
-
"/v1/leads/subscribe": {
|
|
882
|
-
post: {
|
|
883
|
-
operationId: "subscribeLead",
|
|
884
|
-
summary: "Add an email to the newsletter list",
|
|
885
|
-
description:
|
|
886
|
-
"Adds an email address to the given siteId's newsletter list. `source` is " +
|
|
887
|
-
"a free-form tag the caller sets to identify the signup channel (for " +
|
|
888
|
-
"example `agent`); it is stored as-is and not checked against a fixed " +
|
|
889
|
-
"vocabulary. A 201 response means the subscription landed — a duplicate " +
|
|
890
|
-
"email for the same site returns the existing subscriber's id rather than " +
|
|
891
|
-
"erroring.",
|
|
892
|
-
requestBody: {
|
|
893
|
-
required: true,
|
|
894
|
-
content: {
|
|
895
|
-
"application/json": {
|
|
896
|
-
schema: { $ref: "#/components/schemas/LeadSubscribeRequest" },
|
|
897
|
-
},
|
|
898
|
-
},
|
|
899
|
-
},
|
|
900
|
-
responses: {
|
|
901
|
-
"201": {
|
|
902
|
-
description: "Subscription captured",
|
|
903
|
-
content: {
|
|
904
|
-
"application/json": {
|
|
905
|
-
schema: { $ref: "#/components/schemas/LeadWriteResponse" },
|
|
906
|
-
},
|
|
907
|
-
},
|
|
908
|
-
},
|
|
909
|
-
"400": {
|
|
910
|
-
description: "Validation error — invalid request body",
|
|
911
|
-
content: {
|
|
912
|
-
"application/json": {
|
|
913
|
-
schema: { $ref: "#/components/schemas/InvalidInputResponse" },
|
|
914
|
-
},
|
|
915
|
-
},
|
|
916
|
-
},
|
|
917
|
-
"503": {
|
|
918
|
-
description:
|
|
919
|
-
"Lead capture unavailable — persistence failed, delivery failed, or " +
|
|
920
|
-
"this deployment has nowhere to put the lead",
|
|
921
|
-
content: {
|
|
922
|
-
"application/json": {
|
|
923
|
-
schema: {
|
|
924
|
-
oneOf: [
|
|
925
|
-
{ $ref: "#/components/schemas/PersistFailedResponse" },
|
|
926
|
-
{
|
|
927
|
-
$ref: "#/components/schemas/LeadCaptureNotConfiguredResponse",
|
|
928
|
-
},
|
|
929
|
-
],
|
|
930
|
-
},
|
|
931
|
-
},
|
|
932
|
-
},
|
|
933
|
-
},
|
|
934
|
-
},
|
|
935
|
-
},
|
|
936
|
-
},
|
|
937
|
-
"/v1/requests/{id}/confirm": {
|
|
938
|
-
get: {
|
|
939
|
-
operationId: "getRequestConfirmForm",
|
|
940
|
-
summary: "Open a callback request's confirm form",
|
|
941
|
-
description:
|
|
942
|
-
"Renders the site owner's confirm form for one callback request " +
|
|
943
|
-
"(#5970). The `token` query parameter is a one-time, expiring " +
|
|
944
|
-
"credential emailed to the owner when the request landed; it is " +
|
|
945
|
-
"the sole gate on this route, so there is no auth header. This " +
|
|
946
|
-
"operation answers `text/html` rather than JSON — it is a page a " +
|
|
947
|
-
"person opens from a mail client, not an agent surface. A token " +
|
|
948
|
-
"that is tampered with, expired, already used, or valid for a " +
|
|
949
|
-
"different request all answer the same 404. Requires the " +
|
|
950
|
-
"`commerce` licence claim; without it the route answers 404, " +
|
|
951
|
-
"indistinguishable from a build without the module.",
|
|
952
|
-
parameters: [
|
|
953
|
-
{
|
|
954
|
-
name: "id",
|
|
955
|
-
in: "path",
|
|
956
|
-
required: true,
|
|
957
|
-
schema: { type: "string" },
|
|
958
|
-
description: "The callback request's id.",
|
|
959
|
-
},
|
|
960
|
-
{
|
|
961
|
-
name: "token",
|
|
962
|
-
in: "query",
|
|
963
|
-
required: true,
|
|
964
|
-
schema: { type: "string" },
|
|
965
|
-
description: "The one-time confirm credential from the email.",
|
|
966
|
-
},
|
|
967
|
-
],
|
|
968
|
-
responses: {
|
|
969
|
-
"200": {
|
|
970
|
-
description: "The confirm form",
|
|
971
|
-
content: { "text/html": { schema: { type: "string" } } },
|
|
972
|
-
},
|
|
973
|
-
"404": {
|
|
974
|
-
description:
|
|
975
|
-
"No live request for that id and token — or the module is not " +
|
|
976
|
-
"licensed",
|
|
977
|
-
content: { "text/html": { schema: { type: "string" } } },
|
|
978
|
-
},
|
|
979
|
-
},
|
|
980
|
-
},
|
|
981
|
-
post: {
|
|
982
|
-
operationId: "confirmRequest",
|
|
983
|
-
summary: "Confirm a callback request and send a payment link",
|
|
984
|
-
description:
|
|
985
|
-
"Stamps the confirmed time, creates one Stripe Payment Link on " +
|
|
986
|
-
"the site's own Stripe account, stores its id on the request, and " +
|
|
987
|
-
"emails the customer that hosted URL (#5970). The credential is " +
|
|
988
|
-
"spent in the same statement that stamps the confirmation, so a " +
|
|
989
|
-
"second submission answers 404 and no second Payment Link is ever " +
|
|
990
|
-
"created. The body carries EITHER `amountMinor` + `currency` (a " +
|
|
991
|
-
"price set here and now) OR `lookupKey` (a price already in the " +
|
|
992
|
-
"site's Stripe catalogue) — both, or neither, is a 400 before any " +
|
|
993
|
-
"Stripe call. Sandbox-only: a live Stripe key is refused before " +
|
|
994
|
-
"the credential is claimed.",
|
|
995
|
-
parameters: [
|
|
996
|
-
{
|
|
997
|
-
name: "id",
|
|
998
|
-
in: "path",
|
|
999
|
-
required: true,
|
|
1000
|
-
schema: { type: "string" },
|
|
1001
|
-
description: "The callback request's id.",
|
|
1002
|
-
},
|
|
1003
|
-
],
|
|
1004
|
-
requestBody: {
|
|
1005
|
-
required: true,
|
|
1006
|
-
content: {
|
|
1007
|
-
"application/x-www-form-urlencoded": {
|
|
1008
|
-
schema: {
|
|
1009
|
-
type: "object",
|
|
1010
|
-
required: ["token", "confirmedTime", "buyerEmail"],
|
|
1011
|
-
properties: {
|
|
1012
|
-
token: {
|
|
1013
|
-
type: "string",
|
|
1014
|
-
description: "The one-time confirm credential.",
|
|
1015
|
-
},
|
|
1016
|
-
confirmedTime: {
|
|
1017
|
-
type: "string",
|
|
1018
|
-
description:
|
|
1019
|
-
"The agreed time, as the site owner typed it.",
|
|
1020
|
-
},
|
|
1021
|
-
buyerEmail: {
|
|
1022
|
-
type: "string",
|
|
1023
|
-
format: "email",
|
|
1024
|
-
description: "Where the payment link is sent.",
|
|
1025
|
-
},
|
|
1026
|
-
amountMinor: {
|
|
1027
|
-
type: "integer",
|
|
1028
|
-
minimum: 1,
|
|
1029
|
-
description:
|
|
1030
|
-
"The price in MINOR units. Mutually exclusive with " +
|
|
1031
|
-
"lookupKey, and capped by AILK_REQUEST_CONFIRM_MAX_MINOR.",
|
|
1032
|
-
},
|
|
1033
|
-
currency: {
|
|
1034
|
-
type: "string",
|
|
1035
|
-
description:
|
|
1036
|
-
"Lowercase ISO-4217. Defaults to the site's configured " +
|
|
1037
|
-
"currency. Only meaningful beside amountMinor.",
|
|
1038
|
-
},
|
|
1039
|
-
lookupKey: {
|
|
1040
|
-
type: "string",
|
|
1041
|
-
description:
|
|
1042
|
-
"An existing Stripe price's lookup_key. Mutually " +
|
|
1043
|
-
"exclusive with amountMinor.",
|
|
1044
|
-
},
|
|
1045
|
-
},
|
|
1046
|
-
},
|
|
1047
|
-
},
|
|
1048
|
-
},
|
|
1049
|
-
},
|
|
1050
|
-
responses: {
|
|
1051
|
-
"200": {
|
|
1052
|
-
description:
|
|
1053
|
-
"Confirmed — the page carries the hosted Stripe payment URL",
|
|
1054
|
-
content: { "text/html": { schema: { type: "string" } } },
|
|
1055
|
-
},
|
|
1056
|
-
"400": {
|
|
1057
|
-
description:
|
|
1058
|
-
"Invalid form, both or neither price path, an amount above the " +
|
|
1059
|
-
"site's ceiling, or a lookup key no active Stripe price matches",
|
|
1060
|
-
content: { "text/html": { schema: { type: "string" } } },
|
|
1061
|
-
},
|
|
1062
|
-
"404": {
|
|
1063
|
-
description:
|
|
1064
|
-
"No live request for that id and token — or the module is not " +
|
|
1065
|
-
"licensed",
|
|
1066
|
-
content: { "text/html": { schema: { type: "string" } } },
|
|
1067
|
-
},
|
|
1068
|
-
"500": {
|
|
1069
|
-
description: "Stripe is not configured, or the key is not test-mode",
|
|
1070
|
-
content: { "text/html": { schema: { type: "string" } } },
|
|
1071
|
-
},
|
|
1072
|
-
"502": {
|
|
1073
|
-
description:
|
|
1074
|
-
"Stripe could not create the Payment Link. The request is " +
|
|
1075
|
-
"confirmed; the credential is spent.",
|
|
1076
|
-
content: { "text/html": { schema: { type: "string" } } },
|
|
1077
|
-
},
|
|
1078
|
-
},
|
|
1079
|
-
},
|
|
1080
|
-
},
|
|
1081
|
-
"/v1/leads/callback": {
|
|
1082
|
-
post: {
|
|
1083
|
-
operationId: "requestCallback",
|
|
1084
|
-
summary: "Request a callback",
|
|
1085
|
-
description:
|
|
1086
|
-
"Stores a name, phone number, optional preferred time, and an arbitrary " +
|
|
1087
|
-
"payload for the given siteId so a site operator can call the lead back. " +
|
|
1088
|
-
"Unlike submit/subscribe there is no `source` or `email` field. A 201 " +
|
|
1089
|
-
"response means the request landed, in the database or (if the database " +
|
|
1090
|
-
"is unavailable) a file fallback; the response's `email` field reports, " +
|
|
1091
|
-
"best-effort, whether the site operator was notified.",
|
|
1092
|
-
requestBody: {
|
|
1093
|
-
required: true,
|
|
1094
|
-
content: {
|
|
1095
|
-
"application/json": {
|
|
1096
|
-
schema: { $ref: "#/components/schemas/LeadCallbackRequest" },
|
|
1097
|
-
},
|
|
1098
|
-
},
|
|
1099
|
-
},
|
|
1100
|
-
responses: {
|
|
1101
|
-
"201": {
|
|
1102
|
-
description: "Callback request captured",
|
|
1103
|
-
content: {
|
|
1104
|
-
"application/json": {
|
|
1105
|
-
schema: { $ref: "#/components/schemas/LeadWriteResponse" },
|
|
1106
|
-
},
|
|
1107
|
-
},
|
|
1108
|
-
},
|
|
1109
|
-
"400": {
|
|
1110
|
-
description: "Validation error — invalid request body",
|
|
1111
|
-
content: {
|
|
1112
|
-
"application/json": {
|
|
1113
|
-
schema: { $ref: "#/components/schemas/InvalidInputResponse" },
|
|
1114
|
-
},
|
|
1115
|
-
},
|
|
1116
|
-
},
|
|
1117
|
-
"503": {
|
|
1118
|
-
description:
|
|
1119
|
-
"Lead capture unavailable — persistence failed, delivery failed, or " +
|
|
1120
|
-
"this deployment has nowhere to put the lead",
|
|
1121
|
-
content: {
|
|
1122
|
-
"application/json": {
|
|
1123
|
-
schema: {
|
|
1124
|
-
oneOf: [
|
|
1125
|
-
{ $ref: "#/components/schemas/PersistFailedResponse" },
|
|
1126
|
-
{
|
|
1127
|
-
$ref: "#/components/schemas/LeadCaptureNotConfiguredResponse",
|
|
1128
|
-
},
|
|
1129
|
-
{ $ref: "#/components/schemas/DeliveryFailedResponse" },
|
|
1130
|
-
],
|
|
1131
|
-
},
|
|
1132
|
-
},
|
|
1133
|
-
},
|
|
1134
|
-
},
|
|
1135
|
-
},
|
|
1136
|
-
},
|
|
1137
|
-
},
|
|
1138
|
-
"/v1/content/types": {
|
|
1139
|
-
get: {
|
|
1140
|
-
operationId: "listContentTypes",
|
|
1141
|
-
summary: "List supported page types",
|
|
1142
|
-
description:
|
|
1143
|
-
"Returns the canonical schema.org page types this content API can serve.",
|
|
1144
|
-
responses: {
|
|
1145
|
-
"200": {
|
|
1146
|
-
description: "OK",
|
|
1147
|
-
content: {
|
|
1148
|
-
"application/json": {
|
|
1149
|
-
schema: { $ref: "#/components/schemas/TypesResponse" },
|
|
1150
|
-
},
|
|
1151
|
-
},
|
|
1152
|
-
},
|
|
1153
|
-
"429": { $ref: "#/components/responses/RateLimited" },
|
|
1154
|
-
},
|
|
1155
|
-
},
|
|
1156
|
-
},
|
|
1157
|
-
"/v1/content/search": {
|
|
1158
|
-
get: {
|
|
1159
|
-
operationId: "searchContent",
|
|
1160
|
-
summary: "Search published pages by keyword",
|
|
1161
|
-
description:
|
|
1162
|
-
"Keyword search across published pages, scored on title, description and body and returned highest-first. The HTTP twin of the `search_content` MCP tool — the same arguments return the same result set. Optional `category` filters to pages whose `categories` frontmatter includes the given LEAF slug from the closed site-category registry; unlike `locale`, a slug outside the registry is a 400 rather than a silent fallback. Optional locale query parameter selects the locale to search; invalid or unknown locales silently fall back to the project's default locale (AC-i18n-07).",
|
|
1163
|
-
parameters: [
|
|
1164
|
-
{
|
|
1165
|
-
name: "q",
|
|
1166
|
-
in: "query",
|
|
1167
|
-
required: true,
|
|
1168
|
-
description:
|
|
1169
|
-
"The search string. 1–200 characters; the ceiling matches the `search_content` MCP tool's own cap.",
|
|
1170
|
-
schema: { type: "string", minLength: 1, maxLength: 200 },
|
|
1171
|
-
},
|
|
1172
|
-
{
|
|
1173
|
-
name: "type",
|
|
1174
|
-
in: "query",
|
|
1175
|
-
required: false,
|
|
1176
|
-
schema: { type: "string", enum: typeEnum },
|
|
1177
|
-
},
|
|
1178
|
-
{
|
|
1179
|
-
name: "category",
|
|
1180
|
-
in: "query",
|
|
1181
|
-
required: false,
|
|
1182
|
-
description:
|
|
1183
|
-
"A leaf slug from the site-category registry. Group slugs are not valid values — a group is always derived from its leaves.",
|
|
1184
|
-
schema: { type: "string", enum: categoryEnum },
|
|
1185
|
-
},
|
|
1186
|
-
{
|
|
1187
|
-
name: "limit",
|
|
1188
|
-
in: "query",
|
|
1189
|
-
required: false,
|
|
1190
|
-
schema: {
|
|
1191
|
-
type: "integer",
|
|
1192
|
-
minimum: 1,
|
|
1193
|
-
maximum: 50,
|
|
1194
|
-
default: 10,
|
|
1195
|
-
},
|
|
1196
|
-
},
|
|
1197
|
-
{
|
|
1198
|
-
name: "locale",
|
|
1199
|
-
in: "query",
|
|
1200
|
-
required: false,
|
|
1201
|
-
description:
|
|
1202
|
-
"BCP-47 locale tag (e.g. `en`, `es`, `zh-CN`). Invalid or unknown locales silently fall back to the project's default locale.",
|
|
1203
|
-
schema: { type: "string", pattern: "^[a-z]{2}(-[A-Z]{2})?$" },
|
|
1204
|
-
},
|
|
1205
|
-
],
|
|
1206
|
-
responses: {
|
|
1207
|
-
"200": {
|
|
1208
|
-
description: "OK",
|
|
1209
|
-
content: {
|
|
1210
|
-
"application/json": {
|
|
1211
|
-
schema: { $ref: "#/components/schemas/SearchResponse" },
|
|
1212
|
-
},
|
|
1213
|
-
},
|
|
1214
|
-
},
|
|
1215
|
-
"400": { $ref: "#/components/responses/BadRequest" },
|
|
1216
|
-
"429": { $ref: "#/components/responses/RateLimited" },
|
|
1217
|
-
"500": { $ref: "#/components/responses/ServerError" },
|
|
1218
|
-
},
|
|
1219
|
-
},
|
|
1220
|
-
},
|
|
1221
|
-
"/v1/content/{type}": {
|
|
1222
|
-
get: {
|
|
1223
|
-
operationId: "listPagesByType",
|
|
1224
|
-
summary: "List pages of a given type",
|
|
1225
|
-
description:
|
|
1226
|
-
"Paginated list of pages for the given schema.org type. Defaults: page=1, pageSize=20. pageSize is capped at 100. Optional locale query parameter selects the locale to list from; invalid or unknown locales silently fall back to the project's default locale (AC-i18n-07).",
|
|
1227
|
-
parameters: [
|
|
1228
|
-
{
|
|
1229
|
-
name: "type",
|
|
1230
|
-
in: "path",
|
|
1231
|
-
required: true,
|
|
1232
|
-
schema: { type: "string", enum: typeEnum },
|
|
1233
|
-
},
|
|
1234
|
-
{
|
|
1235
|
-
name: "page",
|
|
1236
|
-
in: "query",
|
|
1237
|
-
required: false,
|
|
1238
|
-
schema: { type: "integer", minimum: 1, default: 1 },
|
|
1239
|
-
},
|
|
1240
|
-
{
|
|
1241
|
-
name: "pageSize",
|
|
1242
|
-
in: "query",
|
|
1243
|
-
required: false,
|
|
1244
|
-
schema: {
|
|
1245
|
-
type: "integer",
|
|
1246
|
-
minimum: 1,
|
|
1247
|
-
maximum: 100,
|
|
1248
|
-
default: 20,
|
|
1249
|
-
},
|
|
1250
|
-
},
|
|
1251
|
-
{
|
|
1252
|
-
name: "locale",
|
|
1253
|
-
in: "query",
|
|
1254
|
-
required: false,
|
|
1255
|
-
description:
|
|
1256
|
-
"BCP-47 locale tag (e.g. `en`, `es`, `zh-CN`). Invalid or unknown locales silently fall back to the project's default locale.",
|
|
1257
|
-
schema: { type: "string", pattern: "^[a-z]{2}(-[A-Z]{2})?$" },
|
|
1258
|
-
},
|
|
1259
|
-
],
|
|
1260
|
-
responses: {
|
|
1261
|
-
"200": {
|
|
1262
|
-
description: "OK",
|
|
1263
|
-
content: {
|
|
1264
|
-
"application/json": {
|
|
1265
|
-
schema: { $ref: "#/components/schemas/ListResponse" },
|
|
1266
|
-
},
|
|
1267
|
-
},
|
|
1268
|
-
},
|
|
1269
|
-
"400": { $ref: "#/components/responses/BadRequest" },
|
|
1270
|
-
"429": { $ref: "#/components/responses/RateLimited" },
|
|
1271
|
-
"500": { $ref: "#/components/responses/ServerError" },
|
|
1272
|
-
},
|
|
1273
|
-
},
|
|
1274
|
-
},
|
|
1275
|
-
"/v1/content/{type}/{slug}": {
|
|
1276
|
-
get: {
|
|
1277
|
-
operationId: "getPageBySlug",
|
|
1278
|
-
summary: "Get a single page by type and slug",
|
|
1279
|
-
description:
|
|
1280
|
-
"Returns a single page with resolved schema.org JSON-LD spread at the document root, plus the raw frontmatter under `page`. Optional locale query parameter selects the locale to read from; invalid or unknown locales silently fall back to the project's default locale (AC-i18n-07). Optional `include=body` adds the raw published MDX body under `body`; a deployment whose content adapter cannot serve a raw body answers 400.",
|
|
1281
|
-
parameters: [
|
|
1282
|
-
{
|
|
1283
|
-
name: "type",
|
|
1284
|
-
in: "path",
|
|
1285
|
-
required: true,
|
|
1286
|
-
schema: { type: "string", enum: typeEnum },
|
|
1287
|
-
},
|
|
1288
|
-
{
|
|
1289
|
-
name: "slug",
|
|
1290
|
-
in: "path",
|
|
1291
|
-
required: true,
|
|
1292
|
-
schema: { type: "string", minLength: 1 },
|
|
1293
|
-
},
|
|
1294
|
-
{
|
|
1295
|
-
name: "locale",
|
|
1296
|
-
in: "query",
|
|
1297
|
-
required: false,
|
|
1298
|
-
description:
|
|
1299
|
-
"BCP-47 locale tag (e.g. `en`, `es`, `zh-CN`). Invalid or unknown locales silently fall back to the project's default locale.",
|
|
1300
|
-
schema: { type: "string", pattern: "^[a-z]{2}(-[A-Z]{2})?$" },
|
|
1301
|
-
},
|
|
1302
|
-
{
|
|
1303
|
-
name: "include",
|
|
1304
|
-
in: "query",
|
|
1305
|
-
required: false,
|
|
1306
|
-
description:
|
|
1307
|
-
"Opt-in extra field. `body` adds the raw published MDX body under `body`; omitting the parameter serves the base shape unchanged (#5890).",
|
|
1308
|
-
schema: { type: "string", enum: ["body"] },
|
|
1309
|
-
},
|
|
1310
|
-
],
|
|
1311
|
-
responses: {
|
|
1312
|
-
"200": {
|
|
1313
|
-
description: "OK",
|
|
1314
|
-
content: {
|
|
1315
|
-
"application/json": {
|
|
1316
|
-
schema: { $ref: "#/components/schemas/PageResponse" },
|
|
1317
|
-
},
|
|
1318
|
-
},
|
|
1319
|
-
},
|
|
1320
|
-
"400": { $ref: "#/components/responses/BadRequest" },
|
|
1321
|
-
"404": { $ref: "#/components/responses/NotFound" },
|
|
1322
|
-
"429": { $ref: "#/components/responses/RateLimited" },
|
|
1323
|
-
"500": { $ref: "#/components/responses/ServerError" },
|
|
1324
|
-
},
|
|
1325
|
-
},
|
|
1326
|
-
},
|
|
1327
|
-
}),
|
|
1328
|
-
components: {
|
|
1329
|
-
schemas: {
|
|
1330
|
-
CheckoutSessionRequest: {
|
|
1331
|
-
type: "object",
|
|
1332
|
-
required: ["productCode", "successUrl", "cancelUrl"],
|
|
1333
|
-
properties: {
|
|
1334
|
-
productCode: {
|
|
1335
|
-
type: "string",
|
|
1336
|
-
pattern: "^[A-Za-z0-9._-]{1,200}$",
|
|
1337
|
-
description:
|
|
1338
|
-
"Consumer-defined product code; resolved to a Stripe price via lookup_key",
|
|
1339
|
-
},
|
|
1340
|
-
successUrl: {
|
|
1341
|
-
type: "string",
|
|
1342
|
-
description:
|
|
1343
|
-
"Redirect URL on successful payment (must be a valid http:// or https:// URL)",
|
|
1344
|
-
},
|
|
1345
|
-
cancelUrl: {
|
|
1346
|
-
type: "string",
|
|
1347
|
-
description:
|
|
1348
|
-
"Redirect URL when customer cancels checkout (must be a valid http:// or https:// URL)",
|
|
1349
|
-
},
|
|
1350
|
-
quantity: {
|
|
1351
|
-
type: "integer",
|
|
1352
|
-
minimum: 1,
|
|
1353
|
-
maximum: 999,
|
|
1354
|
-
default: 1,
|
|
1355
|
-
description: "Line-item quantity; defaults to 1",
|
|
1356
|
-
},
|
|
1357
|
-
allowPromotionCodes: {
|
|
1358
|
-
type: "boolean",
|
|
1359
|
-
description:
|
|
1360
|
-
"Show the promotion-code field on the hosted Checkout page. Omit to leave it off",
|
|
1361
|
-
},
|
|
1362
|
-
},
|
|
1363
|
-
},
|
|
1364
|
-
CheckoutSessionResponse: {
|
|
1365
|
-
type: "object",
|
|
1366
|
-
required: ["sessionId", "url"],
|
|
1367
|
-
properties: {
|
|
1368
|
-
sessionId: {
|
|
1369
|
-
type: "string",
|
|
1370
|
-
description: "Stripe Checkout Session ID (cs_test_… / cs_…)",
|
|
1371
|
-
},
|
|
1372
|
-
url: {
|
|
1373
|
-
type: "string",
|
|
1374
|
-
description: "Hosted Checkout page URL",
|
|
1375
|
-
},
|
|
1376
|
-
},
|
|
1377
|
-
},
|
|
1378
|
-
ValidationErrorResponse: {
|
|
1379
|
-
type: "object",
|
|
1380
|
-
required: ["error"],
|
|
1381
|
-
properties: {
|
|
1382
|
-
error: { type: "string", enum: ["validation-error"] },
|
|
1383
|
-
details: {
|
|
1384
|
-
type: "array",
|
|
1385
|
-
items: { type: "object", additionalProperties: true },
|
|
1386
|
-
description: "Zod validation issue list",
|
|
1387
|
-
},
|
|
1388
|
-
},
|
|
1389
|
-
},
|
|
1390
|
-
UnknownProductCodeResponse: {
|
|
1391
|
-
type: "object",
|
|
1392
|
-
required: ["error"],
|
|
1393
|
-
properties: {
|
|
1394
|
-
error: { type: "string", enum: ["unknown-product-code"] },
|
|
1395
|
-
},
|
|
1396
|
-
},
|
|
1397
|
-
LiveModeForbiddenResponse: {
|
|
1398
|
-
type: "object",
|
|
1399
|
-
required: ["error"],
|
|
1400
|
-
properties: {
|
|
1401
|
-
error: { type: "string", enum: ["live-mode-forbidden"] },
|
|
1402
|
-
},
|
|
1403
|
-
},
|
|
1404
|
-
CheckoutUnavailableResponse: {
|
|
1405
|
-
type: "object",
|
|
1406
|
-
required: ["error"],
|
|
1407
|
-
properties: {
|
|
1408
|
-
error: { type: "string", enum: ["checkout-unavailable"] },
|
|
1409
|
-
},
|
|
1410
|
-
},
|
|
1411
|
-
// #6084 D3 — the six-key allow-list. Nothing else may appear here;
|
|
1412
|
-
// AC-3's own test asserts set equality against exactly these names.
|
|
1413
|
-
CheckoutSessionReadResponse: {
|
|
1414
|
-
type: "object",
|
|
1415
|
-
required: [
|
|
1416
|
-
"id",
|
|
1417
|
-
"status",
|
|
1418
|
-
"paymentStatus",
|
|
1419
|
-
"currency",
|
|
1420
|
-
"amountTotal",
|
|
1421
|
-
"lineItems",
|
|
1422
|
-
],
|
|
1423
|
-
properties: {
|
|
1424
|
-
id: {
|
|
1425
|
-
type: "string",
|
|
1426
|
-
description: "The echoed cs_… id, for the buyer to quote to support",
|
|
1427
|
-
},
|
|
1428
|
-
status: {
|
|
1429
|
-
type: "string",
|
|
1430
|
-
enum: ["open", "complete", "expired"],
|
|
1431
|
-
},
|
|
1432
|
-
paymentStatus: {
|
|
1433
|
-
type: "string",
|
|
1434
|
-
enum: ["paid", "unpaid", "no_payment_required"],
|
|
1435
|
-
},
|
|
1436
|
-
currency: {
|
|
1437
|
-
type: "string",
|
|
1438
|
-
description: "ISO-4217 currency code, lowercase",
|
|
1439
|
-
},
|
|
1440
|
-
amountTotal: {
|
|
1441
|
-
type: ["integer", "null"],
|
|
1442
|
-
description: "Session total, in minor currency units",
|
|
1443
|
-
},
|
|
1444
|
-
lineItems: {
|
|
1445
|
-
type: "array",
|
|
1446
|
-
items: {
|
|
1447
|
-
type: "object",
|
|
1448
|
-
required: ["description", "quantity", "amountTotal", "currency"],
|
|
1449
|
-
properties: {
|
|
1450
|
-
description: {
|
|
1451
|
-
type: "string",
|
|
1452
|
-
description: "Stripe's own line-item description (the product name)",
|
|
1453
|
-
},
|
|
1454
|
-
quantity: { type: "integer" },
|
|
1455
|
-
amountTotal: {
|
|
1456
|
-
type: "integer",
|
|
1457
|
-
description: "Line total, in minor currency units",
|
|
1458
|
-
},
|
|
1459
|
-
currency: { type: "string" },
|
|
1460
|
-
},
|
|
1461
|
-
},
|
|
1462
|
-
},
|
|
1463
|
-
},
|
|
1464
|
-
},
|
|
1465
|
-
UnknownSessionResponse: {
|
|
1466
|
-
type: "object",
|
|
1467
|
-
required: ["error"],
|
|
1468
|
-
properties: {
|
|
1469
|
-
error: { type: "string", enum: ["unknown-session"] },
|
|
1470
|
-
},
|
|
1471
|
-
},
|
|
1472
|
-
LeadSubmitRequest: {
|
|
1473
|
-
type: "object",
|
|
1474
|
-
additionalProperties: false,
|
|
1475
|
-
required: ["siteId", "source", "email", "payload"],
|
|
1476
|
-
properties: {
|
|
1477
|
-
siteId: {
|
|
1478
|
-
type: "string",
|
|
1479
|
-
pattern: "^[a-zA-Z0-9._-]{1,128}$",
|
|
1480
|
-
description: "Site/tenant identifier",
|
|
1481
|
-
},
|
|
1482
|
-
source: {
|
|
1483
|
-
type: "string",
|
|
1484
|
-
pattern: "^[^\\r\\n]{1,64}$",
|
|
1485
|
-
description:
|
|
1486
|
-
"Free-form caller-set channel tag (e.g. `agent`); no CR/LF",
|
|
1487
|
-
},
|
|
1488
|
-
email: { type: "string", format: "email", maxLength: 254 },
|
|
1489
|
-
consent: {
|
|
1490
|
-
type: "boolean",
|
|
1491
|
-
description:
|
|
1492
|
-
"`true` when the visitor accepted the site's terms. Required (as " +
|
|
1493
|
-
"`true`) for a consent-bearing lead — a contact lead or a job " +
|
|
1494
|
-
"application; read for no other source, and never " +
|
|
1495
|
-
"stored as sent — the server records `payload.consent` instead.",
|
|
1496
|
-
},
|
|
1497
|
-
payload: {
|
|
1498
|
-
type: "object",
|
|
1499
|
-
maxProperties: 64,
|
|
1500
|
-
additionalProperties: true,
|
|
1501
|
-
description: "Arbitrary form fields, up to 64 keys",
|
|
1502
|
-
},
|
|
1503
|
-
},
|
|
1504
|
-
},
|
|
1505
|
-
LeadSubscribeRequest: {
|
|
1506
|
-
type: "object",
|
|
1507
|
-
additionalProperties: false,
|
|
1508
|
-
required: ["siteId", "email", "source"],
|
|
1509
|
-
properties: {
|
|
1510
|
-
siteId: {
|
|
1511
|
-
type: "string",
|
|
1512
|
-
pattern: "^[a-zA-Z0-9._-]{1,128}$",
|
|
1513
|
-
description: "Site/tenant identifier",
|
|
1514
|
-
},
|
|
1515
|
-
email: { type: "string", format: "email", maxLength: 254 },
|
|
1516
|
-
source: {
|
|
1517
|
-
type: "string",
|
|
1518
|
-
pattern: "^[^\\r\\n]{1,64}$",
|
|
1519
|
-
description:
|
|
1520
|
-
"Free-form caller-set channel tag (e.g. `agent`); no CR/LF",
|
|
1521
|
-
},
|
|
1522
|
-
},
|
|
1523
|
-
},
|
|
1524
|
-
LeadCallbackRequest: {
|
|
1525
|
-
type: "object",
|
|
1526
|
-
additionalProperties: false,
|
|
1527
|
-
required: ["siteId", "name", "phone", "payload"],
|
|
1528
|
-
properties: {
|
|
1529
|
-
siteId: {
|
|
1530
|
-
type: "string",
|
|
1531
|
-
pattern: "^[a-zA-Z0-9._-]{1,128}$",
|
|
1532
|
-
description: "Site/tenant identifier",
|
|
1533
|
-
},
|
|
1534
|
-
name: { type: "string", minLength: 1, maxLength: 128 },
|
|
1535
|
-
phone: { type: "string", minLength: 3, maxLength: 32 },
|
|
1536
|
-
preferredTime: { type: "string", maxLength: 64 },
|
|
1537
|
-
payload: {
|
|
1538
|
-
type: "object",
|
|
1539
|
-
maxProperties: 64,
|
|
1540
|
-
additionalProperties: true,
|
|
1541
|
-
description: "Arbitrary form fields, up to 64 keys",
|
|
1542
|
-
},
|
|
1543
|
-
},
|
|
1544
|
-
},
|
|
1545
|
-
LeadEmailResult: {
|
|
1546
|
-
description:
|
|
1547
|
-
"Best-effort operator-notification outcome. `skipped` means email " +
|
|
1548
|
-
"notification isn't configured; persistence already succeeded either way.",
|
|
1549
|
-
oneOf: [
|
|
1550
|
-
{
|
|
1551
|
-
type: "object",
|
|
1552
|
-
required: ["ok", "messageId"],
|
|
1553
|
-
properties: {
|
|
1554
|
-
ok: { type: "boolean", enum: [true] },
|
|
1555
|
-
messageId: { type: "string" },
|
|
1556
|
-
},
|
|
1557
|
-
},
|
|
1558
|
-
{
|
|
1559
|
-
type: "object",
|
|
1560
|
-
required: ["ok", "error"],
|
|
1561
|
-
properties: {
|
|
1562
|
-
ok: { type: "boolean", enum: [false] },
|
|
1563
|
-
error: { type: "string" },
|
|
1564
|
-
},
|
|
1565
|
-
},
|
|
1566
|
-
{
|
|
1567
|
-
type: "object",
|
|
1568
|
-
required: ["skipped", "reason"],
|
|
1569
|
-
properties: {
|
|
1570
|
-
skipped: { type: "boolean", enum: [true] },
|
|
1571
|
-
reason: { type: "string" },
|
|
1572
|
-
},
|
|
1573
|
-
},
|
|
1574
|
-
],
|
|
1575
|
-
},
|
|
1576
|
-
LeadWriteResponse: {
|
|
1577
|
-
type: "object",
|
|
1578
|
-
required: ["id", "persisted", "email"],
|
|
1579
|
-
properties: {
|
|
1580
|
-
id: {
|
|
1581
|
-
type: "string",
|
|
1582
|
-
description:
|
|
1583
|
-
"Server-assigned record id — a DB-prefixed entity id when " +
|
|
1584
|
-
'persisted is "db", a `fallback-<ts>-<rand>` file id when ' +
|
|
1585
|
-
'persisted is "file", or an `email-<ts>-<rand>` id when persisted ' +
|
|
1586
|
-
'is "email" (no database; the operator email was the delivery)',
|
|
1587
|
-
},
|
|
1588
|
-
persisted: { type: "string", enum: ["db", "file", "email"] },
|
|
1589
|
-
email: { $ref: "#/components/schemas/LeadEmailResult" },
|
|
1590
|
-
},
|
|
1591
|
-
},
|
|
1592
|
-
InvalidInputResponse: {
|
|
1593
|
-
type: "object",
|
|
1594
|
-
required: ["error", "message"],
|
|
1595
|
-
properties: {
|
|
1596
|
-
error: { type: "string", enum: ["invalid_input"] },
|
|
1597
|
-
message: { type: "string" },
|
|
1598
|
-
},
|
|
1599
|
-
},
|
|
1600
|
-
ConsentRequiredResponse: {
|
|
1601
|
-
type: "object",
|
|
1602
|
-
required: ["error", "message"],
|
|
1603
|
-
properties: {
|
|
1604
|
-
error: { type: "string", enum: ["consent_required"] },
|
|
1605
|
-
message: { type: "string" },
|
|
1606
|
-
},
|
|
1607
|
-
},
|
|
1608
|
-
PersistFailedResponse: {
|
|
1609
|
-
type: "object",
|
|
1610
|
-
required: ["error"],
|
|
1611
|
-
properties: {
|
|
1612
|
-
error: { type: "string", enum: ["persist_failed"] },
|
|
1613
|
-
},
|
|
1614
|
-
},
|
|
1615
|
-
DeliveryFailedResponse: {
|
|
1616
|
-
type: "object",
|
|
1617
|
-
required: ["error", "message"],
|
|
1618
|
-
properties: {
|
|
1619
|
-
error: { type: "string", enum: ["delivery_failed"] },
|
|
1620
|
-
message: { type: "string" },
|
|
1621
|
-
},
|
|
1622
|
-
},
|
|
1623
|
-
LeadCaptureStatus: {
|
|
1624
|
-
type: "object",
|
|
1625
|
-
required: ["contact", "newsletter"],
|
|
1626
|
-
properties: {
|
|
1627
|
-
contact: { type: "boolean" },
|
|
1628
|
-
newsletter: { type: "boolean" },
|
|
1629
|
-
},
|
|
1630
|
-
},
|
|
1631
|
-
LeadCaptureNotConfiguredResponse: {
|
|
1632
|
-
type: "object",
|
|
1633
|
-
required: ["error", "message"],
|
|
1634
|
-
properties: {
|
|
1635
|
-
error: { type: "string", enum: ["lead_capture_not_configured"] },
|
|
1636
|
-
message: { type: "string" },
|
|
1637
|
-
},
|
|
1638
|
-
},
|
|
1639
|
-
ContentPage: {
|
|
1640
|
-
type: "object",
|
|
1641
|
-
required: ["type", "slug", "title", "description"],
|
|
1642
|
-
properties: {
|
|
1643
|
-
type: { type: "string", enum: typeEnum },
|
|
1644
|
-
slug: { type: "string" },
|
|
1645
|
-
title: { type: "string" },
|
|
1646
|
-
description: { type: "string" },
|
|
1647
|
-
publishedAt: { type: "string" },
|
|
1648
|
-
updatedAt: { type: "string" },
|
|
1649
|
-
image: { type: "string" },
|
|
1650
|
-
schema: { type: "object", additionalProperties: true },
|
|
1651
|
-
},
|
|
1652
|
-
},
|
|
1653
|
-
Pagination: {
|
|
1654
|
-
type: "object",
|
|
1655
|
-
required: ["page", "pageSize", "totalItems", "totalPages"],
|
|
1656
|
-
properties: {
|
|
1657
|
-
page: { type: "integer", minimum: 1 },
|
|
1658
|
-
pageSize: { type: "integer", minimum: 1 },
|
|
1659
|
-
totalItems: { type: "integer", minimum: 0 },
|
|
1660
|
-
totalPages: { type: "integer", minimum: 1 },
|
|
1661
|
-
},
|
|
1662
|
-
},
|
|
1663
|
-
TypesResponse: {
|
|
1664
|
-
type: "object",
|
|
1665
|
-
required: ["@context", "@type", "types"],
|
|
1666
|
-
properties: {
|
|
1667
|
-
"@context": { type: "string", enum: ["https://schema.org"] },
|
|
1668
|
-
"@type": { type: "string", enum: ["ItemList"] },
|
|
1669
|
-
types: {
|
|
1670
|
-
type: "array",
|
|
1671
|
-
items: { type: "string", enum: typeEnum },
|
|
1672
|
-
},
|
|
1673
|
-
},
|
|
1674
|
-
},
|
|
1675
|
-
SearchResult: {
|
|
1676
|
-
type: "object",
|
|
1677
|
-
required: ["slug", "type", "title", "snippet", "score"],
|
|
1678
|
-
properties: {
|
|
1679
|
-
slug: { type: "string" },
|
|
1680
|
-
type: { type: "string", enum: typeEnum },
|
|
1681
|
-
title: { type: "string" },
|
|
1682
|
-
snippet: { type: "string" },
|
|
1683
|
-
score: { type: "number" },
|
|
1684
|
-
},
|
|
1685
|
-
},
|
|
1686
|
-
SearchResponse: {
|
|
1687
|
-
type: "object",
|
|
1688
|
-
required: ["@context", "@type", "query", "items", "totalItems"],
|
|
1689
|
-
properties: {
|
|
1690
|
-
"@context": { type: "string", enum: ["https://schema.org"] },
|
|
1691
|
-
"@type": { type: "string", enum: ["ItemList"] },
|
|
1692
|
-
query: { type: "string" },
|
|
1693
|
-
items: {
|
|
1694
|
-
type: "array",
|
|
1695
|
-
items: { $ref: "#/components/schemas/SearchResult" },
|
|
1696
|
-
},
|
|
1697
|
-
totalItems: { type: "integer", minimum: 0 },
|
|
1698
|
-
},
|
|
1699
|
-
},
|
|
1700
|
-
ListResponse: {
|
|
1701
|
-
type: "object",
|
|
1702
|
-
required: ["@context", "@type", "type", "items", "pagination"],
|
|
1703
|
-
properties: {
|
|
1704
|
-
"@context": { type: "string", enum: ["https://schema.org"] },
|
|
1705
|
-
"@type": { type: "string", enum: ["ItemList"] },
|
|
1706
|
-
type: { type: "string", enum: typeEnum },
|
|
1707
|
-
items: {
|
|
1708
|
-
type: "array",
|
|
1709
|
-
items: { $ref: "#/components/schemas/ContentPage" },
|
|
1710
|
-
},
|
|
1711
|
-
pagination: { $ref: "#/components/schemas/Pagination" },
|
|
1712
|
-
},
|
|
1713
|
-
},
|
|
1714
|
-
PageResponse: {
|
|
1715
|
-
type: "object",
|
|
1716
|
-
required: ["@context", "@type", "page"],
|
|
1717
|
-
properties: {
|
|
1718
|
-
"@context": { type: "string", enum: ["https://schema.org"] },
|
|
1719
|
-
"@type": { type: "string" },
|
|
1720
|
-
page: { $ref: "#/components/schemas/ContentPage" },
|
|
1721
|
-
body: {
|
|
1722
|
-
type: "string",
|
|
1723
|
-
description:
|
|
1724
|
-
"Raw published MDX body. Present only when the request carried `include=body` (#5890).",
|
|
1725
|
-
},
|
|
1726
|
-
},
|
|
1727
|
-
additionalProperties: true,
|
|
1728
|
-
},
|
|
1729
|
-
Appointment: {
|
|
1730
|
-
type: "object",
|
|
1731
|
-
required: [
|
|
1732
|
-
"id",
|
|
1733
|
-
"startTime",
|
|
1734
|
-
"endTime",
|
|
1735
|
-
"title",
|
|
1736
|
-
"attendeeName",
|
|
1737
|
-
"attendeeEmail",
|
|
1738
|
-
"status",
|
|
1739
|
-
"paid",
|
|
1740
|
-
],
|
|
1741
|
-
properties: {
|
|
1742
|
-
id: { type: "string" },
|
|
1743
|
-
startTime: { type: "string", format: "date-time" },
|
|
1744
|
-
endTime: { type: "string", format: "date-time" },
|
|
1745
|
-
title: { type: "string", nullable: true },
|
|
1746
|
-
attendeeName: { type: "string" },
|
|
1747
|
-
attendeeEmail: { type: "string", format: "email" },
|
|
1748
|
-
status: { type: "string", enum: ["pending", "confirmed", "cancelled"] },
|
|
1749
|
-
paid: { type: "boolean" },
|
|
1750
|
-
},
|
|
1751
|
-
},
|
|
1752
|
-
AppointmentListResponse: {
|
|
1753
|
-
type: "object",
|
|
1754
|
-
required: ["ok", "data"],
|
|
1755
|
-
properties: {
|
|
1756
|
-
ok: { type: "boolean", enum: [true] },
|
|
1757
|
-
data: {
|
|
1758
|
-
type: "object",
|
|
1759
|
-
required: ["appointments", "nextCursor"],
|
|
1760
|
-
properties: {
|
|
1761
|
-
appointments: {
|
|
1762
|
-
type: "array",
|
|
1763
|
-
items: { $ref: "#/components/schemas/Appointment" },
|
|
1764
|
-
},
|
|
1765
|
-
nextCursor: { type: "string", nullable: true },
|
|
1766
|
-
},
|
|
1767
|
-
},
|
|
1768
|
-
},
|
|
1769
|
-
},
|
|
1770
|
-
ErrorResponse: {
|
|
1771
|
-
type: "object",
|
|
1772
|
-
required: ["error"],
|
|
1773
|
-
properties: {
|
|
1774
|
-
error: { type: "string" },
|
|
1775
|
-
details: {},
|
|
1776
|
-
},
|
|
1777
|
-
},
|
|
1778
|
-
},
|
|
1779
|
-
responses: {
|
|
1780
|
-
BadRequest: {
|
|
1781
|
-
description: "Invalid request parameters",
|
|
1782
|
-
content: {
|
|
1783
|
-
"application/json": {
|
|
1784
|
-
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
1785
|
-
},
|
|
1786
|
-
},
|
|
1787
|
-
},
|
|
1788
|
-
NotFound: {
|
|
1789
|
-
description: "Resource not found",
|
|
1790
|
-
content: {
|
|
1791
|
-
"application/json": {
|
|
1792
|
-
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
1793
|
-
},
|
|
1794
|
-
},
|
|
1795
|
-
},
|
|
1796
|
-
RateLimited: {
|
|
1797
|
-
description: "Too many requests — rate limit exceeded",
|
|
1798
|
-
content: {
|
|
1799
|
-
"application/json": {
|
|
1800
|
-
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
1801
|
-
},
|
|
1802
|
-
},
|
|
1803
|
-
},
|
|
1804
|
-
ServerError: {
|
|
1805
|
-
description: "Internal server error",
|
|
1806
|
-
content: {
|
|
1807
|
-
"application/json": {
|
|
1808
|
-
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
1809
|
-
},
|
|
1810
|
-
},
|
|
1811
|
-
},
|
|
1812
|
-
},
|
|
1813
|
-
},
|
|
1814
|
-
};
|
|
1815
|
-
}
|