@lukoweb/apitogo 0.1.126 → 0.1.129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/cli.js CHANGED
@@ -736,6 +736,8 @@ var init_apitogo_config_core = __esm({
736
736
  DEMOMODE: "demoMode",
737
737
  DISPLAYNAME: "displayName",
738
738
  PRICEAMOUNT: "priceAmount",
739
+ PRICINGMODEL: "pricingModel",
740
+ PRICELABEL: "priceLabel",
739
741
  PRICECURRENCY: "priceCurrency",
740
742
  BILLINGPERIOD: "billingPeriod",
741
743
  LIMITSJSON: "limitsJson",
@@ -1020,6 +1022,10 @@ var init_ModulesSchema = __esm({
1020
1022
  sku: z3.string(),
1021
1023
  displayName: z3.string(),
1022
1024
  priceAmount: z3.number().default(0),
1025
+ pricingModel: z3.enum(["fixed", "custom"]).optional().describe(
1026
+ '"custom" shows a contact-sales form instead of checkout; priceAmount must be 0.'
1027
+ ),
1028
+ priceLabel: z3.string().max(64).optional().describe('Text shown instead of the price, e.g. "Custom".'),
1023
1029
  priceCurrency: z3.string().trim().toLowerCase().regex(
1024
1030
  /^[a-z]{3}$/,
1025
1031
  "priceCurrency must be a 3-letter ISO 4217 code (lowercase)"
@@ -1127,6 +1133,8 @@ function plansToPreviewCatalog(plans) {
1127
1133
  sku: plan.sku,
1128
1134
  displayName: plan.displayName,
1129
1135
  priceAmount: plan.priceAmount ?? 0,
1136
+ pricingModel: plan.pricingModel ?? "fixed",
1137
+ ...plan.priceLabel ? { priceLabel: plan.priceLabel } : {},
1130
1138
  priceCurrency: plan.priceCurrency ?? "usd",
1131
1139
  billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
1132
1140
  limits: coerceGatewayLimits(plan) ?? null,
@@ -1175,13 +1183,14 @@ async function readEffectiveManifest(rootDir, _env) {
1175
1183
  return null;
1176
1184
  }
1177
1185
  }
1178
- var BillingPeriodSchema, GatewayLimitsSchema, PriceCurrencySchema, ALLOWED_RUNTIME_VERSIONS, ManifestPlanInputSchema, DevPortalLocalManifestSchema;
1186
+ var BillingPeriodSchema, PlanPricingModelSchema, GatewayLimitsSchema, PriceCurrencySchema, ALLOWED_RUNTIME_VERSIONS, ManifestPlanInputSchema, DevPortalLocalManifestSchema;
1179
1187
  var init_local_manifest = __esm({
1180
1188
  "src/config/local-manifest.ts"() {
1181
1189
  init_apitogo_config_loader();
1182
1190
  init_gateway_limits();
1183
1191
  init_landing_manifest();
1184
1192
  BillingPeriodSchema = z4.enum(["month", "year", "monthly", "annual"]);
1193
+ PlanPricingModelSchema = z4.enum(["fixed", "custom"]);
1185
1194
  GatewayLimitsSchema = z4.object({
1186
1195
  rateLimit: z4.object({
1187
1196
  calls: z4.number(),
@@ -1212,6 +1221,9 @@ var init_local_manifest = __esm({
1212
1221
  sku: z4.string().min(1),
1213
1222
  displayName: z4.string().min(1),
1214
1223
  priceAmount: z4.number().default(0),
1224
+ /** `custom` plans show a contact-sales form instead of checkout. */
1225
+ pricingModel: PlanPricingModelSchema.optional(),
1226
+ priceLabel: z4.string().max(64).optional(),
1215
1227
  priceCurrency: PriceCurrencySchema.optional(),
1216
1228
  billingPeriod: BillingPeriodSchema.optional(),
1217
1229
  limits: GatewayLimitsSchema.optional(),
@@ -5797,7 +5809,7 @@ import {
5797
5809
  // package.json
5798
5810
  var package_default = {
5799
5811
  name: "@lukoweb/apitogo",
5800
- version: "0.1.126",
5812
+ version: "0.1.129",
5801
5813
  type: "module",
5802
5814
  sideEffects: [
5803
5815
  "**/*.css",
@@ -13134,7 +13146,25 @@ function validateManifestForPlatformPublish(manifest) {
13134
13146
  if (plan.priceAmount !== void 0 && plan.priceAmount < 0) {
13135
13147
  issues.push({
13136
13148
  field: `plans[${index}].priceAmount`,
13137
- message: "priceAmount must be >= 0."
13149
+ message: 'priceAmount must be >= 0. For negotiated pricing use pricingModel: "custom".'
13150
+ });
13151
+ }
13152
+ if (plan.pricingModel !== void 0 && plan.pricingModel !== "fixed" && plan.pricingModel !== "custom") {
13153
+ issues.push({
13154
+ field: `plans[${index}].pricingModel`,
13155
+ message: "pricingModel must be 'fixed' or 'custom'."
13156
+ });
13157
+ }
13158
+ if (plan.pricingModel === "custom" && (plan.priceAmount ?? 0) !== 0) {
13159
+ issues.push({
13160
+ field: `plans[${index}].priceAmount`,
13161
+ message: "priceAmount must be 0 (or omitted) for custom-priced plans."
13162
+ });
13163
+ }
13164
+ if (plan.priceLabel !== void 0 && plan.priceLabel.length > 64) {
13165
+ issues.push({
13166
+ field: `plans[${index}].priceLabel`,
13167
+ message: "priceLabel must be at most 64 characters."
13138
13168
  });
13139
13169
  }
13140
13170
  const currencyResult = tryNormalizePriceCurrency(plan.priceCurrency);
@@ -15582,6 +15612,11 @@ function buildPlanItemFromWizard(input5) {
15582
15612
  priceAmount: input5.priceAmount,
15583
15613
  limits: buildLimits(input5.rateLimitCalls, input5.monthlyQuota)
15584
15614
  };
15615
+ if (input5.pricingModel === "custom") {
15616
+ item.priceAmount = 0;
15617
+ item.pricingModel = "custom";
15618
+ return item;
15619
+ }
15585
15620
  if (input5.priceAmount > 0) {
15586
15621
  item.priceCurrency = "usd";
15587
15622
  item.billingPeriod = "month";
@@ -15946,11 +15981,13 @@ async function promptMakeConfigInteractive(options) {
15946
15981
  printDiagnosticsToConsole("Plan title is required.");
15947
15982
  continue;
15948
15983
  }
15949
- const priceAmount = await askNonNegativeNumber(
15984
+ const pricingAnswer = (await ask(
15950
15985
  rl,
15951
- "Price (USD/month, 0 for free)",
15952
- 0
15953
- );
15986
+ "Pricing: fixed price or custom (contact sales)? (fixed/custom)",
15987
+ "fixed"
15988
+ )).toLowerCase();
15989
+ const pricingModel = pricingAnswer.startsWith("c") ? "custom" : "fixed";
15990
+ const priceAmount = pricingModel === "custom" ? 0 : await askNonNegativeNumber(rl, "Price (USD/month, 0 for free)", 0);
15954
15991
  const rateLimitCalls = await askNonNegativeNumber(
15955
15992
  rl,
15956
15993
  "Rate limit (calls per minute)",
@@ -15964,6 +16001,7 @@ async function promptMakeConfigInteractive(options) {
15964
16001
  const planItem = buildPlanItemFromWizard({
15965
16002
  displayName: displayName.trim(),
15966
16003
  priceAmount,
16004
+ pricingModel,
15967
16005
  rateLimitCalls,
15968
16006
  monthlyQuota
15969
16007
  });
@@ -16167,7 +16205,7 @@ async function ensureTsconfigJson(dir) {
16167
16205
  return true;
16168
16206
  }
16169
16207
  }
16170
- var LANDING_PAGE_SOURCE = 'import {\n ApiShowcaseSection,\n FeaturesSection,\n HeroSection,\n LandingShell,\n PlansPreviewSection,\n PricingCtaSection,\n StepsSection,\n} from "@lukoweb/apitogo-module-landing";\n\nexport default function LandingPage() {\n return (\n <LandingShell\n title="Test against a real API before you build one"\n description="Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA."\n >\n <HeroSection\n hero={{\n badge: "SANDBOX API",\n title: "Test against a real API before you build one",\n description:\n "Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA. No setup, no infrastructure.",\n }}\n primaryAction={{ label: "Start for free", href: "/signup" }}\n secondaryAction={{ label: "Read the docs", href: "/introduction" }}\n codeExamples={[\n {\n filename: "Create a key",\n language: "bash",\n code: `$ curl -X POST https://api.sampleapi.dev/v1/keys\n# sk_live_9f2c7a1e...`,\n },\n {\n filename: "Call an endpoint",\n language: "bash",\n code: `$ curl https://api.sampleapi.dev/v1/users/42 \\\\\n -H "Authorization: Bearer sk_live_9f2c..."\n# { "id": 42, "name": "Priya Shah", "plan": "pro" }`,\n },\n ]}\n />\n <FeaturesSection\n section={{\n label: "Everything a real API has",\n title: "Built for testing, not just browsing",\n description:\n "Realistic data, live auth, rate limits, and webhooks \u2014 so you can ship with confidence.",\n }}\n features={[\n {\n title: "Realistic mock data",\n description:\n "Users, orders, products, and posts with relationships and pagination, not lorem ipsum.",\n icon: "boxes",\n },\n {\n title: "Real auth flow",\n description:\n "Every request checks a live API key, so you can test error handling and expiry.",\n icon: "shield-check",\n },\n {\n title: "Rate limiting",\n description:\n "Per-plan request limits and 429 responses, so you can build and test backoff logic.",\n icon: "gauge",\n },\n {\n title: "Webhooks",\n description:\n "Trigger test events and inspect delivered payloads and retries.",\n icon: "webhook",\n },\n {\n title: "OpenAPI & docs",\n description:\n "A full OpenAPI spec and reference docs for every endpoint.",\n icon: "book-open",\n },\n {\n title: "Usage analytics",\n description:\n "Track requests, latency, and errors per key from your dashboard.",\n icon: "activity",\n },\n ]}\n />\n <StepsSection\n steps={[\n {\n step: "01",\n title: "Create an account",\n description:\n "Sign up and land straight in your dashboard \u2014 no approval queue.",\n },\n {\n step: "02",\n title: "Generate a key",\n description:\n "Every key is scoped and rate-limited to your plan from the moment it\'s created.",\n },\n {\n step: "03",\n title: "Call any endpoint",\n description:\n "Users, orders, products, posts, and webhooks \u2014 all documented, all live.",\n },\n ]}\n />\n <ApiShowcaseSection\n apiShowcase={{\n title: "Sample endpoints",\n subtitle:\n "A slice of what\'s available \u2014 see the full reference in the docs.",\n browseAllHref: "/api",\n browseAllLabel: "Browse all APIs",\n layout: "list",\n items: [\n {\n name: "List paginated users",\n path: "/v1/users",\n method: "GET",\n summary: "List paginated users",\n },\n {\n name: "Create a mock order",\n path: "/v1/orders",\n method: "POST",\n summary: "Create a mock order",\n },\n {\n name: "Fetch one product",\n path: "/v1/products/{id}",\n method: "GET",\n summary: "Fetch one product",\n },\n {\n name: "List posts with comments",\n path: "/v1/posts",\n method: "GET",\n summary: "List posts with comments",\n },\n {\n name: "Fire a test webhook event",\n path: "/v1/webhooks/test",\n method: "POST",\n summary: "Fire a test webhook event",\n },\n {\n name: "Delete a mock order",\n path: "/v1/orders/{id}",\n method: "DELETE",\n summary: "Delete a mock order",\n },\n ],\n }}\n />\n <PlansPreviewSection\n plans={[\n {\n name: "Free",\n price: "$0",\n unit: "per month",\n blurb: "1,000 requests/mo.",\n },\n {\n name: "Developer",\n price: "$19",\n unit: "per month",\n blurb: "50,000 requests/mo, webhooks included.",\n featured: true,\n },\n {\n name: "Team",\n price: "$79",\n unit: "per month",\n blurb: "500,000 requests/mo, 5 team seats.",\n },\n {\n name: "Enterprise",\n price: "$299",\n unit: "per month",\n blurb: "Unlimited requests, SSO, SLA.",\n },\n ]}\n />\n <PricingCtaSection\n variant="banner"\n pricingCta={{\n title: "Start testing in minutes",\n description:\n "Free tier includes 1,000 requests a month \u2014 no card required.",\n primaryAction: { label: "Start for free", href: "/signup" },\n }}\n />\n </LandingShell>\n );\n}\n';
16208
+ var LANDING_PAGE_SOURCE = 'import {\n ApiShowcaseSection,\n FeaturesSection,\n HeroSection,\n LandingShell,\n PlansPreviewSection,\n PricingCtaSection,\n StepsSection,\n} from "@lukoweb/apitogo-module-landing";\n\nexport default function LandingPage() {\n return (\n <LandingShell\n title="Test against a real API before you build one"\n description="Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA."\n >\n <HeroSection\n hero={{\n badge: "SANDBOX API",\n title: "Test against a real API before you build one",\n description:\n "Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA. No setup, no infrastructure.",\n }}\n primaryAction={{ label: "Start for free", href: "/signup" }}\n secondaryAction={{ label: "Read the docs", href: "/introduction" }}\n codeExamples={[\n {\n filename: "Create a key",\n language: "bash",\n code: `$ curl -X POST https://api.sampleapi.dev/v1/keys\n# sk_live_9f2c7a1e...`,\n },\n {\n filename: "Call an endpoint",\n language: "bash",\n code: `$ curl https://api.sampleapi.dev/v1/users/42 \\\\\n -H "Authorization: Bearer sk_live_9f2c..."\n# { "id": 42, "name": "Priya Shah", "plan": "pro" }`,\n },\n ]}\n />\n <FeaturesSection\n section={{\n label: "Everything a real API has",\n title: "Built for testing, not just browsing",\n description:\n "Realistic data, live auth, rate limits, and webhooks \u2014 so you can ship with confidence.",\n }}\n features={[\n {\n title: "Realistic mock data",\n description:\n "Users, orders, products, and posts with relationships and pagination, not lorem ipsum.",\n icon: "boxes",\n },\n {\n title: "Real auth flow",\n description:\n "Every request checks a live API key, so you can test error handling and expiry.",\n icon: "shield-check",\n },\n {\n title: "Rate limiting",\n description:\n "Per-plan request limits and 429 responses, so you can build and test backoff logic.",\n icon: "gauge",\n },\n {\n title: "Webhooks",\n description:\n "Trigger test events and inspect delivered payloads and retries.",\n icon: "webhook",\n },\n {\n title: "OpenAPI & docs",\n description:\n "A full OpenAPI spec and reference docs for every endpoint.",\n icon: "book-open",\n },\n {\n title: "Usage analytics",\n description:\n "Track requests, latency, and errors per key from your dashboard.",\n icon: "activity",\n },\n ]}\n />\n <StepsSection\n steps={[\n {\n step: "01",\n title: "Create an account",\n description:\n "Sign up and land straight in your dashboard \u2014 no approval queue.",\n },\n {\n step: "02",\n title: "Generate a key",\n description:\n "Every key is scoped and rate-limited to your plan from the moment it\'s created.",\n },\n {\n step: "03",\n title: "Call any endpoint",\n description:\n "Users, orders, products, posts, and webhooks \u2014 all documented, all live.",\n },\n ]}\n />\n {/* Static sample list. To show your real API operations from the dev portal\n backend instead, see https://docs.apitogo.com/docs/customization/landing-page#live-apis */}\n <ApiShowcaseSection\n apiShowcase={{\n title: "Sample endpoints",\n subtitle:\n "A slice of what\'s available \u2014 see the full reference in the docs.",\n browseAllHref: "/api",\n browseAllLabel: "Browse all APIs",\n layout: "list",\n items: [\n {\n name: "List paginated users",\n path: "/v1/users",\n method: "GET",\n summary: "List paginated users",\n },\n {\n name: "Create a mock order",\n path: "/v1/orders",\n method: "POST",\n summary: "Create a mock order",\n },\n {\n name: "Fetch one product",\n path: "/v1/products/{id}",\n method: "GET",\n summary: "Fetch one product",\n },\n {\n name: "List posts with comments",\n path: "/v1/posts",\n method: "GET",\n summary: "List posts with comments",\n },\n {\n name: "Fire a test webhook event",\n path: "/v1/webhooks/test",\n method: "POST",\n summary: "Fire a test webhook event",\n },\n {\n name: "Delete a mock order",\n path: "/v1/orders/{id}",\n method: "DELETE",\n summary: "Delete a mock order",\n },\n ],\n }}\n />\n <PlansPreviewSection\n plans={[\n {\n name: "Free",\n price: "$0",\n unit: "per month",\n blurb: "1,000 requests/mo.",\n },\n {\n name: "Developer",\n price: "$19",\n unit: "per month",\n blurb: "50,000 requests/mo, webhooks included.",\n featured: true,\n },\n {\n name: "Team",\n price: "$79",\n unit: "per month",\n blurb: "500,000 requests/mo, 5 team seats.",\n },\n {\n name: "Enterprise",\n price: "$299",\n unit: "per month",\n blurb: "Unlimited requests, SSO, SLA.",\n },\n ]}\n />\n <PricingCtaSection\n variant="banner"\n pricingCta={{\n title: "Start testing in minutes",\n description:\n "Free tier includes 1,000 requests a month \u2014 no card required.",\n primaryAction: { label: "Start for free", href: "/signup" },\n }}\n />\n </LandingShell>\n );\n}\n';
16171
16209
  var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
16172
16210
  `;
16173
16211
  var DASHBOARD_PAGE_SOURCE = `export { OverviewDashboardPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
@@ -17203,6 +17241,8 @@ async function handleCreateGatewayPlan(client, config2, args) {
17203
17241
  limitsJson: args.limitsJson ?? null,
17204
17242
  includedActionKeys,
17205
17243
  priceAmount: args.priceAmount,
17244
+ pricingModel: args.pricingModel,
17245
+ priceLabel: args.priceLabel,
17206
17246
  priceCurrency: args.priceCurrency,
17207
17247
  billingPeriod: args.billingPeriod,
17208
17248
  sortOrder: args.sortOrder
@@ -17229,6 +17269,8 @@ function buildUpdateGatewayPlanBody(existing, patch) {
17229
17269
  limitsJson: patch.limitsJson !== void 0 ? patch.limitsJson : existing.limitsJson ?? null,
17230
17270
  includedActionKeys: patch.includedActionKeys !== void 0 ? patch.includedActionKeys : existingActionKeys(existing),
17231
17271
  priceAmount: patch.priceAmount ?? existing.priceAmount,
17272
+ pricingModel: patch.pricingModel ?? existing.pricingModel,
17273
+ priceLabel: patch.priceLabel ?? existing.priceLabel ?? void 0,
17232
17274
  priceCurrency: patch.priceCurrency ?? existing.priceCurrency,
17233
17275
  billingPeriod: patch.billingPeriod ?? existing.billingPeriod,
17234
17276
  sortOrder: patch.sortOrder ?? existing.sortOrder
@@ -17509,6 +17551,9 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
17509
17551
  isActive: true,
17510
17552
  limitsJson: planLimitsForPlatform(input5),
17511
17553
  priceAmount: input5.priceAmount,
17554
+ // Config is the source of truth: dropping pricingModel/priceLabel reverts to fixed / no label.
17555
+ pricingModel: input5.pricingModel ?? "fixed",
17556
+ priceLabel: input5.priceLabel ?? "",
17512
17557
  priceCurrency: input5.priceCurrency,
17513
17558
  billingPeriod: input5.billingPeriod ? normalizeBillingPeriod2(input5.billingPeriod) : void 0
17514
17559
  };
@@ -17536,6 +17581,8 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
17536
17581
  sku: input5.sku,
17537
17582
  displayName: input5.displayName,
17538
17583
  priceAmount: input5.priceAmount,
17584
+ pricingModel: input5.pricingModel,
17585
+ priceLabel: input5.priceLabel,
17539
17586
  priceCurrency: input5.priceCurrency ?? "usd",
17540
17587
  billingPeriod: normalizeBillingPeriod2(input5.billingPeriod),
17541
17588
  limitsJson: planLimitsForPlatform(input5),
@@ -18226,17 +18273,14 @@ function isWarmPoolInfrastructureTerminal(project) {
18226
18273
  return status2 === "ready" || status2 === "failed" || status2 === "deleting";
18227
18274
  }
18228
18275
  function shouldSkipColdModuleInfrastructureWait(project, infraStatus) {
18229
- if (isWarmPoolInfrastructureReady(project)) {
18230
- return true;
18231
- }
18232
18276
  const resourceType = infraStatus?.resourceType;
18233
- if (resourceType === "WarmPoolBinding") {
18234
- return true;
18235
- }
18236
18277
  if (resourceType && COLD_MODULE_INFRA_RESOURCE_TYPES.has(resourceType)) {
18237
18278
  return false;
18238
18279
  }
18239
- return false;
18280
+ if (resourceType === "WarmPoolBinding") {
18281
+ return true;
18282
+ }
18283
+ return isWarmPoolInfrastructureReady(project);
18240
18284
  }
18241
18285
  function resolvePortalBackendFqdn(project) {
18242
18286
  const portalApiUrl = project.portalApiUrl?.trim();
@@ -106,6 +106,8 @@ export interface GatewayPlanDto {
106
106
  displayName: string;
107
107
  isActive: boolean;
108
108
  priceAmount?: number;
109
+ pricingModel?: "fixed" | "custom";
110
+ priceLabel?: string | null;
109
111
  priceCurrency?: string;
110
112
  billingPeriod?: string;
111
113
  limitsJson?: string | null;
@@ -120,6 +122,8 @@ export interface CreateGatewayPlanRequest {
120
122
  limitsJson?: string | null;
121
123
  includedActionKeys: string[];
122
124
  priceAmount?: number;
125
+ pricingModel?: "fixed" | "custom";
126
+ priceLabel?: string;
123
127
  priceCurrency?: string;
124
128
  billingPeriod?: string;
125
129
  sortOrder?: number;
@@ -325,6 +329,8 @@ export interface UpdateGatewayPlanRequest {
325
329
  limitsJson?: string | null;
326
330
  includedActionKeys: string[];
327
331
  priceAmount?: number;
332
+ pricingModel?: "fixed" | "custom";
333
+ priceLabel?: string;
328
334
  priceCurrency?: string;
329
335
  billingPeriod?: string;
330
336
  sortOrder?: number;
@@ -34,6 +34,7 @@ export type LandingTrustInput = {
34
34
  export type LandingApiShowcaseItemInput = {
35
35
  name: string;
36
36
  path: string;
37
+ method?: string;
37
38
  icon?: string;
38
39
  latency?: string;
39
40
  summary?: string;
@@ -7,6 +7,8 @@ export interface ManifestPlanInput {
7
7
  sku: string;
8
8
  displayName: string;
9
9
  priceAmount?: number;
10
+ pricingModel?: "fixed" | "custom";
11
+ priceLabel?: string;
10
12
  priceCurrency?: string;
11
13
  billingPeriod?: "month" | "year" | "monthly" | "annual";
12
14
  limits?: GatewayLimits;
@@ -5,6 +5,8 @@ export interface PublishPlanInput {
5
5
  sku: string;
6
6
  displayName: string;
7
7
  priceAmount?: number;
8
+ pricingModel?: string;
9
+ priceLabel?: string;
8
10
  priceCurrency?: string;
9
11
  billingPeriod?: string;
10
12
  limits?: GatewayLimits;
@@ -42,6 +42,8 @@ export declare function handleCreateGatewayPlan(client: PlatformApiClient, confi
42
42
  limitsJson?: string;
43
43
  includedActionKeys?: string[];
44
44
  priceAmount?: number;
45
+ pricingModel?: "fixed" | "custom";
46
+ priceLabel?: string;
45
47
  priceCurrency?: string;
46
48
  billingPeriod?: string;
47
49
  sortOrder?: number;
@@ -59,6 +61,8 @@ export declare function handleUpdateGatewayPlan(client: PlatformApiClient, confi
59
61
  limitsJson?: string;
60
62
  includedActionKeys?: string[];
61
63
  priceAmount?: number;
64
+ pricingModel?: "fixed" | "custom";
65
+ priceLabel?: string;
62
66
  priceCurrency?: string;
63
67
  billingPeriod?: string;
64
68
  sortOrder?: number;
@@ -71,6 +75,8 @@ export declare function handlePatchGatewayPlan(client: PlatformApiClient, config
71
75
  limitsJson?: string | null;
72
76
  includedActionKeys?: string[];
73
77
  priceAmount?: number;
78
+ pricingModel?: "fixed" | "custom";
79
+ priceLabel?: string;
74
80
  priceCurrency?: string;
75
81
  billingPeriod?: string;
76
82
  sortOrder?: number;
@@ -9,11 +9,21 @@ export declare const APITOGO_MANIFEST_ENV_FILES: {
9
9
  };
10
10
  export { APITOGO_CONFIG_FILENAME };
11
11
  export type ApitogoManifestEnv = keyof typeof APITOGO_MANIFEST_ENV_FILES;
12
+ export declare const PlanPricingModelSchema: z.ZodEnum<{
13
+ fixed: "fixed";
14
+ custom: "custom";
15
+ }>;
16
+ export type PlanPricingModel = z.infer<typeof PlanPricingModelSchema>;
12
17
  export declare const ALLOWED_RUNTIME_VERSIONS: readonly ["DOTNET_10", "DOTNET_9", "DOTNET_8", "NODE_24_LTS", "NODE_22_LTS", "PYTHON_314", "PYTHON_313", "PYTHON_312", "PYTHON_311", "PYTHON_310"];
13
18
  export declare const ManifestPlanInputSchema: z.ZodObject<{
14
19
  sku: z.ZodString;
15
20
  displayName: z.ZodString;
16
21
  priceAmount: z.ZodDefault<z.ZodNumber>;
22
+ pricingModel: z.ZodOptional<z.ZodEnum<{
23
+ fixed: "fixed";
24
+ custom: "custom";
25
+ }>>;
26
+ priceLabel: z.ZodOptional<z.ZodString>;
17
27
  priceCurrency: z.ZodOptional<z.ZodString>;
18
28
  billingPeriod: z.ZodOptional<z.ZodEnum<{
19
29
  month: "month";
@@ -34,6 +44,9 @@ export declare const ManifestPlanInputSchema: z.ZodObject<{
34
44
  limitsJson: z.ZodOptional<z.ZodString>;
35
45
  includedActionKeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
36
46
  }, z.core.$strip>;
47
+ export declare function isPlanCustomPriced(plan: {
48
+ pricingModel?: string | null;
49
+ }): boolean;
37
50
  export declare function isPlanFree(plan: {
38
51
  priceAmount?: number;
39
52
  }): boolean;
@@ -68,6 +81,11 @@ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
68
81
  sku: z.ZodString;
69
82
  displayName: z.ZodString;
70
83
  priceAmount: z.ZodDefault<z.ZodNumber>;
84
+ pricingModel: z.ZodOptional<z.ZodEnum<{
85
+ fixed: "fixed";
86
+ custom: "custom";
87
+ }>>;
88
+ priceLabel: z.ZodOptional<z.ZodString>;
71
89
  priceCurrency: z.ZodOptional<z.ZodString>;
72
90
  billingPeriod: z.ZodOptional<z.ZodEnum<{
73
91
  month: "month";
@@ -230,6 +248,8 @@ export type DevPortalPreviewPlan = {
230
248
  sku: string;
231
249
  displayName: string;
232
250
  priceAmount: number;
251
+ pricingModel: PlanPricingModel;
252
+ priceLabel?: string;
233
253
  priceCurrency: string;
234
254
  billingPeriod: string;
235
255
  limits: GatewayLimits | null;
@@ -388,6 +388,11 @@ declare const PlanCatalogItemSchema: z.ZodObject<{
388
388
  sku: z.ZodString;
389
389
  displayName: z.ZodString;
390
390
  priceAmount: z.ZodDefault<z.ZodNumber>;
391
+ pricingModel: z.ZodOptional<z.ZodEnum<{
392
+ fixed: "fixed";
393
+ custom: "custom";
394
+ }>>;
395
+ priceLabel: z.ZodOptional<z.ZodString>;
391
396
  priceCurrency: z.ZodOptional<z.ZodString>;
392
397
  billingPeriod: z.ZodOptional<z.ZodString>;
393
398
  limits: z.ZodOptional<z.ZodObject<{
@@ -421,6 +426,11 @@ export declare const PlansModuleSchema: z.ZodObject<{
421
426
  sku: z.ZodString;
422
427
  displayName: z.ZodString;
423
428
  priceAmount: z.ZodDefault<z.ZodNumber>;
429
+ pricingModel: z.ZodOptional<z.ZodEnum<{
430
+ fixed: "fixed";
431
+ custom: "custom";
432
+ }>>;
433
+ priceLabel: z.ZodOptional<z.ZodString>;
424
434
  priceCurrency: z.ZodOptional<z.ZodString>;
425
435
  billingPeriod: z.ZodOptional<z.ZodString>;
426
436
  limits: z.ZodOptional<z.ZodObject<{
@@ -507,6 +517,11 @@ export declare const UserPanelModuleSchema: z.ZodObject<{
507
517
  sku: z.ZodString;
508
518
  displayName: z.ZodString;
509
519
  priceAmount: z.ZodDefault<z.ZodNumber>;
520
+ pricingModel: z.ZodOptional<z.ZodEnum<{
521
+ fixed: "fixed";
522
+ custom: "custom";
523
+ }>>;
524
+ priceLabel: z.ZodOptional<z.ZodString>;
510
525
  priceCurrency: z.ZodOptional<z.ZodString>;
511
526
  billingPeriod: z.ZodOptional<z.ZodString>;
512
527
  limits: z.ZodOptional<z.ZodObject<{
@@ -705,6 +720,11 @@ export declare const ModulesSchema: z.ZodOptional<z.ZodObject<{
705
720
  sku: z.ZodString;
706
721
  displayName: z.ZodString;
707
722
  priceAmount: z.ZodDefault<z.ZodNumber>;
723
+ pricingModel: z.ZodOptional<z.ZodEnum<{
724
+ fixed: "fixed";
725
+ custom: "custom";
726
+ }>>;
727
+ priceLabel: z.ZodOptional<z.ZodString>;
708
728
  priceCurrency: z.ZodOptional<z.ZodString>;
709
729
  billingPeriod: z.ZodOptional<z.ZodString>;
710
730
  limits: z.ZodOptional<z.ZodObject<{
@@ -7039,6 +7039,11 @@ export declare const ZudokuConfig: z.ZodObject<{
7039
7039
  sku: z.ZodString;
7040
7040
  displayName: z.ZodString;
7041
7041
  priceAmount: z.ZodDefault<z.ZodNumber>;
7042
+ pricingModel: z.ZodOptional<z.ZodEnum<{
7043
+ fixed: "fixed";
7044
+ custom: "custom";
7045
+ }>>;
7046
+ priceLabel: z.ZodOptional<z.ZodString>;
7042
7047
  priceCurrency: z.ZodOptional<z.ZodString>;
7043
7048
  billingPeriod: z.ZodOptional<z.ZodString>;
7044
7049
  limits: z.ZodOptional<z.ZodObject<{
@@ -68,6 +68,14 @@ export type _Schema37 = {
68
68
  sku: string
69
69
  displayName: string
70
70
  priceAmount?: number
71
+ /**
72
+ * "custom" shows a contact-sales form instead of checkout; priceAmount must be 0.
73
+ */
74
+ pricingModel?: ("fixed" | "custom")
75
+ /**
76
+ * Text shown instead of the price, e.g. "Custom".
77
+ */
78
+ priceLabel?: string
71
79
  priceCurrency?: string
72
80
  billingPeriod?: string
73
81
  limits?: {
@@ -86,31 +94,31 @@ export type _Schema37 = {
86
94
  limitsJson?: string
87
95
  includedActionKeys?: string[]
88
96
  }[]
89
- export type _Schema38 = ({
97
+ export type _Schema40 = ({
90
98
  type: "url"
91
- input: (string | _Schema39[])
92
- server?: _Schema40
93
- path?: _Schema41
94
- categories?: _Schema42
95
- options?: _Schema44
99
+ input: (string | _Schema41[])
100
+ server?: _Schema42
101
+ path?: _Schema43
102
+ categories?: _Schema44
103
+ options?: _Schema46
96
104
  } | {
97
105
  type: "file"
98
- input: (string | (string | _Schema39)[])
99
- server?: _Schema40
100
- path?: _Schema41
101
- categories?: _Schema42
102
- options?: _Schema44
106
+ input: (string | (string | _Schema41)[])
107
+ server?: _Schema42
108
+ path?: _Schema43
109
+ categories?: _Schema44
110
+ options?: _Schema46
103
111
  } | {
104
112
  type: "raw"
105
113
  input: string
106
- server?: _Schema40
107
- path?: _Schema41
108
- categories?: _Schema42
109
- options?: _Schema44
114
+ server?: _Schema42
115
+ path?: _Schema43
116
+ categories?: _Schema44
117
+ options?: _Schema46
110
118
  })
111
- export type _Schema40 = string
112
- export type _Schema41 = string
113
- export type _Schema42 = {
119
+ export type _Schema42 = string
120
+ export type _Schema43 = string
121
+ export type _Schema44 = {
114
122
  label: string
115
123
  tags: string[]
116
124
  }[]
@@ -412,8 +420,8 @@ export interface FlatZudokuConfig {
412
420
  landing?: _Schema16
413
421
  userPanel?: _Schema25
414
422
  }
415
- apis?: (_Schema38 | _Schema38[])
416
- catalogs?: (_Schema45 | _Schema45[])
423
+ apis?: (_Schema40 | _Schema40[])
424
+ catalogs?: (_Schema47 | _Schema47[])
417
425
  apiKeys?: {
418
426
  enabled: boolean
419
427
  getConsumers?: unknown
@@ -441,7 +449,7 @@ export interface FlatZudokuConfig {
441
449
  }
442
450
  enableStatusPages?: boolean
443
451
  defaults?: {
444
- apis: _Schema44
452
+ apis: _Schema46
445
453
  examplesLanguage?: string
446
454
  }
447
455
  __pluginDirs?: string[]
@@ -731,12 +739,12 @@ export interface _Schema36 {
731
739
  description?: string
732
740
  }[]
733
741
  }
734
- export interface _Schema39 {
742
+ export interface _Schema41 {
735
743
  path: string
736
744
  input: string
737
745
  label?: string
738
746
  }
739
- export interface _Schema44 {
747
+ export interface _Schema46 {
740
748
  examplesLanguage?: string
741
749
  supportedLanguages?: {
742
750
  value: string
@@ -753,7 +761,7 @@ export interface _Schema44 {
753
761
  transformExamples?: unknown
754
762
  generateCodeSnippet?: unknown
755
763
  }
756
- export interface _Schema45 {
764
+ export interface _Schema47 {
757
765
  path: string
758
766
  label: string
759
767
  items?: string[]
@@ -67,6 +67,10 @@ npx apitogo config
67
67
  - **`sku`** — stable identifier (used in checkout URLs and gateway sync).
68
68
  - **`displayName`** — label on `/pricing`.
69
69
  - **`priceAmount`** — `0` for free tiers; greater than zero for paid plans (USD by default).
70
+ - **`pricingModel`** — `fixed` (default) or `custom`. See
71
+ [Custom pricing](#custom-pricing-contact-sales).
72
+ - **`priceLabel`** — optional text shown instead of the price, such as `"Let's talk"` (max 64
73
+ characters).
70
74
  - **`limits`** — rate limit and quota policies (see below). Encoded `limitsJson` strings are still
71
75
  accepted on load.
72
76
 
@@ -90,6 +94,28 @@ OpenAPI is deployed, you can scope a plan to specific operations with `includedA
90
94
  example `get:/orders/{id}`). Edit those fields in `apitogo.config.json` — see
91
95
  [Manifest](./manifest.md).
92
96
 
97
+ ## Custom pricing (contact sales)
98
+
99
+ Set `pricingModel: "custom"` for plans with negotiated prices, such as an Enterprise tier. Leave
100
+ `priceAmount` at `0` or omit it.
101
+
102
+ ```json
103
+ {
104
+ "sku": "enterprise",
105
+ "displayName": "Enterprise",
106
+ "pricingModel": "custom",
107
+ "priceLabel": "Let's talk"
108
+ }
109
+ ```
110
+
111
+ On `/pricing`, custom plans show `priceLabel` (or **Custom**) and a **Contact sales** button. The
112
+ button opens a public `/contact-sales` form instead of Stripe checkout or registration. Visitors
113
+ don't need an account.
114
+
115
+ Each submission is emailed to the project's **sales contact email**, or to the project owner's email
116
+ when none is set. Custom plans can't be subscribed to through checkout. Custom plans are available
117
+ on every platform plan, including Free.
118
+
93
119
  ## Publish
94
120
 
95
121
  Gateway plans are applied when you run:
@@ -0,0 +1,159 @@
1
+ ---
2
+ sidebar_icon: house
3
+ title: Landing Page
4
+ description:
5
+ Customize your dev portal home page and show your real API operations in the API showcase, loaded
6
+ live from your dev portal backend.
7
+ ---
8
+
9
+ A scaffolded dev portal home page lives in `landing/LandingPage.tsx`. It composes section components
10
+ from `@lukoweb/apitogo-module-landing` inside a `LandingShell`:
11
+
12
+ | Component | Purpose |
13
+ | --------------------- | --------------------------------------------- |
14
+ | `HeroSection` | Headline, description, CTAs and code examples |
15
+ | `TrustSection` | Logos or social proof |
16
+ | `FeaturesSection` | Feature grid |
17
+ | `StepsSection` | Numbered "how it works" steps |
18
+ | `ApiShowcaseSection` | Grid or method + path list of your API |
19
+ | `PlansPreviewSection` | Short plan comparison linking to `/pricing` |
20
+ | `PricingCtaSection` | Closing call to action |
21
+
22
+ Edit the file directly to add, remove or reorder sections. The site footer is configured separately
23
+ in `apitogo.config.json` under `modules.landing.content.footer` (see
24
+ [Footer](/docs/configuration/footer)).
25
+
26
+ ## API showcase
27
+
28
+ `ApiShowcaseSection` renders the items you pass it. The scaffold ships with a **static sample
29
+ list**:
30
+
31
+ ```tsx
32
+ <ApiShowcaseSection
33
+ apiShowcase={{
34
+ title: "Sample endpoints",
35
+ layout: "list", // or "grid"
36
+ browseAllHref: "/api",
37
+ items: [
38
+ { name: "List users", path: "/v1/users", method: "GET", summary: "List paginated users" },
39
+ ],
40
+ }}
41
+ />
42
+ ```
43
+
44
+ Each item accepts `name`, `path`, and optionally `method`, `summary`, `icon` and `latency`.
45
+
46
+ ## Live APIs
47
+
48
+ Instead of maintaining the list by hand, you can load your API's real operations. Your dev portal
49
+ backend syncs the **gateway actions** of your project (every operation discovered from the OpenAPI
50
+ spec you deployed) and serves them publicly at `GET /api/v1/actions`. The `useGatewayActions()` hook
51
+ fetches that list; you decide what to show and pass the result to `ApiShowcaseSection`.
52
+
53
+ ```tsx
54
+ import {
55
+ ApiShowcaseSection,
56
+ LandingShell,
57
+ toApiShowcaseItems,
58
+ useGatewayActions,
59
+ } from "@lukoweb/apitogo-module-landing";
60
+
61
+ const staticItems = [
62
+ { name: "List users", path: "/v1/users", method: "GET", summary: "List paginated users" },
63
+ ];
64
+
65
+ // Optional friendly labels, keyed by action key (`method:path`, lower-case method).
66
+ const labels: Record<string, string> = {
67
+ "get:/v1/users": "List paginated users",
68
+ "post:/v1/orders": "Create an order",
69
+ };
70
+
71
+ export default function LandingPage() {
72
+ const { data } = useGatewayActions({ inPlan: true });
73
+
74
+ const liveItems = toApiShowcaseItems(
75
+ (data?.actions ?? []).filter((action) => action.path.startsWith("/v1/")).slice(0, 6),
76
+ labels,
77
+ );
78
+
79
+ return (
80
+ <LandingShell title="My API">
81
+ <ApiShowcaseSection
82
+ apiShowcase={{
83
+ title: "Our APIs",
84
+ layout: "list",
85
+ browseAllHref: "/api",
86
+ items: liveItems.length > 0 ? liveItems : staticItems,
87
+ }}
88
+ />
89
+ </LandingShell>
90
+ );
91
+ }
92
+ ```
93
+
94
+ ### Filters
95
+
96
+ `useGatewayActions()` accepts server-side filters. All filters are combined:
97
+
98
+ | Option | Type | Description |
99
+ | ------------ | --------- | ------------------------------------------------------ |
100
+ | `inPlan` | `boolean` | Only actions included in at least one active plan |
101
+ | `plan` | `string` | Only actions included in the plan with this SKU |
102
+ | `isPublic` | `boolean` | Only actions callable without an API key (or only not) |
103
+ | `method` | `string` | HTTP method, e.g. `GET` |
104
+ | `pathPrefix` | `string` | Path prefix, e.g. `/v1/orders` |
105
+ | `limit` | `number` | Maximum number of actions (server cap: 500) |
106
+ | `enabled` | `boolean` | Set to `false` to skip the request |
107
+
108
+ Anything else — sorting, renaming, grouping, picking a hand-curated subset — is up to you in plain
109
+ TypeScript before you build the items.
110
+
111
+ Each action has this shape:
112
+
113
+ ```ts
114
+ type GatewayAction = {
115
+ actionKey: string; // e.g. "get:/orders/{id}"
116
+ method: string; // e.g. "GET"
117
+ path: string; // e.g. "/orders/{id}"
118
+ isPublic: boolean; // callable without an API key
119
+ };
120
+ ```
121
+
122
+ Actions only carry the method and path. Use the `summaries` argument of `toApiShowcaseItems()` (or
123
+ your own mapping) to add human-friendly names.
124
+
125
+ ### Always keep a fallback
126
+
127
+ The hook runs in the browser only, so the prerendered HTML never contains a stale list. `data` is
128
+ `undefined`:
129
+
130
+ - while the request is loading,
131
+ - if the request fails,
132
+ - in preview and demo mode, or when no dev portal backend URL is configured (for example in
133
+ `npm run dev` before your portal is provisioned).
134
+
135
+ `ApiShowcaseSection` renders nothing for an empty list, so pass a static fallback as shown above.
136
+
137
+ ### What becomes public
138
+
139
+ `GET /api/v1/actions` is anonymous: **every operation in the OpenAPI spec deployed to your gateway
140
+ is listable**, including ones you may not document in the portal. Use `inPlan: true` to limit the
141
+ list to operations your plans actually sell, and remove internal operations from the spec you deploy
142
+ to the gateway if they must not be discoverable.
143
+
144
+ ### Freshness and limits
145
+
146
+ - The backend syncs actions at startup and on the plan sync interval (15 minutes by default). A new
147
+ release shows up after the next sync.
148
+ - Responses are cacheable for 5 minutes (`Cache-Control: public, max-age=300`).
149
+ - The endpoint is rate limited per client IP (60 requests per minute).
150
+
151
+ ### Troubleshooting
152
+
153
+ - **The list is always the fallback:** check that `authentication.apiBaseUrl` (or
154
+ `VITE_APITOGO_AUTHENTICATION_API_BASE_URL`) points at your dev portal backend and is not the
155
+ placeholder URL.
156
+ - **CORS errors:** the backend only allows the portal's configured frontend URL. Serving the site
157
+ from another domain requires adding it to the backend's allowed origins.
158
+ - **Empty list on a new portal:** the portal is not activated yet, or the gateway has no release.
159
+ Deploy your backend, then wait for the next sync.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.126",
3
+ "version": "0.1.129",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -167,6 +167,16 @@
167
167
  "default": "./src/cli/platform/apply/index.ts"
168
168
  }
169
169
  },
170
+ "scripts": {
171
+ "build": "esbuild src/cli/cli.ts src/vite/prerender/worker.ts --outdir=dist/cli --entry-names=[name] --bundle --format=esm --packages=external --target=node20 --platform=node --log-level=error",
172
+ "postbuild": "tsx scripts/check-external-deps.ts",
173
+ "typecheck": "tsc --project tsconfig.app.json --noEmit",
174
+ "generate:types": "tsx scripts/generate-types.js && tsx scripts/generate-flat-config.js",
175
+ "build:standalone": "vite build --mode standalone --config vite.standalone.config.ts --log-level=error",
176
+ "prepack": "npm run build && tsx scripts/generate-publish-exports.ts",
177
+ "clean": "rm -rf dist",
178
+ "codegen": "graphql-codegen --config ./src/codegen.ts"
179
+ },
170
180
  "dependencies": {
171
181
  "@apidevtools/json-schema-ref-parser": "15.3.1",
172
182
  "@apidevtools/swagger-parser": "^12.1.0",
@@ -346,13 +356,148 @@
346
356
  "optional": true
347
357
  }
348
358
  },
349
- "scripts": {
350
- "build": "esbuild src/cli/cli.ts src/vite/prerender/worker.ts --outdir=dist/cli --entry-names=[name] --bundle --format=esm --packages=external --target=node20 --platform=node --log-level=error",
351
- "postbuild": "tsx scripts/check-external-deps.ts",
352
- "typecheck": "tsc --project tsconfig.app.json --noEmit",
353
- "generate:types": "tsx scripts/generate-types.js && tsx scripts/generate-flat-config.js",
354
- "build:standalone": "vite build --mode standalone --config vite.standalone.config.ts --log-level=error",
355
- "clean": "rm -rf dist",
356
- "codegen": "graphql-codegen --config ./src/codegen.ts"
359
+ "publishConfig": {
360
+ "exports": {
361
+ ".": {
362
+ "types": "./dist/declarations/index.d.ts",
363
+ "default": "./src/index.ts"
364
+ },
365
+ "./package.json": "./package.json",
366
+ "./ui/*": {
367
+ "types": "./dist/declarations/lib/ui/*.d.ts",
368
+ "default": "./src/lib/ui/*.tsx"
369
+ },
370
+ "./ui/*.js": {
371
+ "types": "./dist/declarations/lib/ui/*.d.ts",
372
+ "default": "./src/lib/ui/*.tsx"
373
+ },
374
+ "./client": {
375
+ "types": "./client.d.ts"
376
+ },
377
+ "./auth/clerk": {
378
+ "types": "./dist/declarations/lib/authentication/providers/clerk.d.ts",
379
+ "default": "./src/lib/authentication/providers/clerk.tsx"
380
+ },
381
+ "./auth/auth0": {
382
+ "types": "./dist/declarations/lib/authentication/providers/auth0.d.ts",
383
+ "default": "./src/lib/authentication/providers/auth0.tsx"
384
+ },
385
+ "./auth/openid": {
386
+ "types": "./dist/declarations/lib/authentication/providers/openid.d.ts",
387
+ "default": "./src/lib/authentication/providers/openid.tsx"
388
+ },
389
+ "./auth/supabase": {
390
+ "types": "./dist/declarations/lib/authentication/providers/supabase.d.ts",
391
+ "default": "./src/lib/authentication/providers/supabase.tsx"
392
+ },
393
+ "./auth/azureb2c": {
394
+ "types": "./dist/declarations/lib/authentication/providers/azureb2c.d.ts",
395
+ "default": "./src/lib/authentication/providers/azureb2c.tsx"
396
+ },
397
+ "./auth/firebase": {
398
+ "types": "./dist/declarations/lib/authentication/providers/firebase.d.ts",
399
+ "default": "./src/lib/authentication/providers/firebase.tsx"
400
+ },
401
+ "./auth/dev-portal": {
402
+ "types": "./dist/declarations/lib/authentication/providers/dev-portal.d.ts",
403
+ "default": "./src/lib/authentication/providers/dev-portal.tsx"
404
+ },
405
+ "./plugins": {
406
+ "types": "./dist/declarations/lib/core/plugins.d.ts",
407
+ "default": "./src/lib/core/plugins.ts"
408
+ },
409
+ "./plugins/api-keys": {
410
+ "types": "./dist/declarations/lib/plugins/api-keys/index.d.ts",
411
+ "default": "./src/lib/plugins/api-keys/index.tsx"
412
+ },
413
+ "./plugins/markdown": {
414
+ "types": "./dist/declarations/lib/plugins/markdown/index.d.ts",
415
+ "default": "./src/lib/plugins/markdown/index.tsx"
416
+ },
417
+ "./plugins/openapi": {
418
+ "types": "./dist/declarations/lib/plugins/openapi/index.d.ts",
419
+ "default": "./src/lib/plugins/openapi/index.tsx"
420
+ },
421
+ "./plugins/custom-pages": {
422
+ "types": "./dist/declarations/lib/plugins/custom-pages/index.d.ts",
423
+ "default": "./src/lib/plugins/custom-pages/index.tsx"
424
+ },
425
+ "./plugins/search-inkeep": {
426
+ "types": "./dist/declarations/lib/plugins/search-inkeep/index.d.ts",
427
+ "default": "./src/lib/plugins/search-inkeep/index.tsx"
428
+ },
429
+ "./plugins/search-pagefind": {
430
+ "types": "./dist/declarations/lib/plugins/search-pagefind/index.d.ts",
431
+ "default": "./src/lib/plugins/search-pagefind/index.tsx"
432
+ },
433
+ "./plugins/api-catalog": {
434
+ "types": "./dist/declarations/lib/plugins/api-catalog/index.d.ts",
435
+ "default": "./src/lib/plugins/api-catalog/index.tsx"
436
+ },
437
+ "./modules/landing": "@lukoweb/apitogo-module-landing",
438
+ "./modules/user-panel": "@lukoweb/apitogo-module-user-panel",
439
+ "./components": {
440
+ "types": "./dist/declarations/lib/components/index.d.ts",
441
+ "default": "./src/lib/components/index.ts"
442
+ },
443
+ "./mermaid": {
444
+ "types": "./dist/declarations/lib/components/Mermaid.d.ts",
445
+ "default": "./src/lib/components/Mermaid.tsx"
446
+ },
447
+ "./router": {
448
+ "types": "./dist/declarations/lib/core/router.d.ts",
449
+ "default": "./src/lib/core/router.ts"
450
+ },
451
+ "./react-query": {
452
+ "types": "./dist/declarations/lib/core/react-query.d.ts",
453
+ "default": "./src/lib/core/react-query.ts"
454
+ },
455
+ "./icons": {
456
+ "types": "./dist/declarations/lib/icons.d.ts",
457
+ "default": "./src/lib/icons.ts"
458
+ },
459
+ "./vite": {
460
+ "types": "./dist/declarations/vite/index.d.ts",
461
+ "default": "./src/vite/index.ts"
462
+ },
463
+ "./app/*": {
464
+ "types": "./dist/declarations/app/*.d.ts",
465
+ "default": "./src/app/*"
466
+ },
467
+ "./hooks": {
468
+ "types": "./dist/declarations/lib/hooks/index.d.ts",
469
+ "default": "./src/lib/hooks/index.ts"
470
+ },
471
+ "./main.css": "./src/app/main.css",
472
+ "./processors/*": {
473
+ "types": "./dist/declarations/lib/plugins/openapi/processors/*.d.ts",
474
+ "default": "./src/lib/plugins/openapi/processors/*.ts"
475
+ },
476
+ "./with-zuplo": {
477
+ "types": "./dist/declarations/zuplo/with-zuplo.d.ts",
478
+ "default": "./src/zuplo/with-zuplo.ts"
479
+ },
480
+ "./testing": {
481
+ "types": "./dist/declarations/lib/testing/index.d.ts",
482
+ "default": "./src/lib/testing/index.tsx"
483
+ },
484
+ "./local-manifest": {
485
+ "types": "./dist/declarations/config/local-manifest.d.ts",
486
+ "default": "./src/config/local-manifest.ts"
487
+ },
488
+ "./build-config": {
489
+ "types": "./dist/declarations/config/build-apitogo-config.d.ts",
490
+ "default": "./src/config/build-apitogo-config.ts"
491
+ },
492
+ "./config-loader": {
493
+ "types": "./dist/declarations/config/apitogo-config-loader.d.ts",
494
+ "default": "./src/config/apitogo-config-loader.ts"
495
+ },
496
+ "./apply": {
497
+ "types": "./dist/declarations/cli/platform/apply/index.d.ts",
498
+ "default": "./src/cli/platform/apply/index.ts"
499
+ }
500
+ },
501
+ "access": "public"
357
502
  }
358
- }
503
+ }
@@ -106,6 +106,8 @@ const COLLAPSED_UPPERCASE_ALIASES: Record<string, string> = {
106
106
  DEMOMODE: "demoMode",
107
107
  DISPLAYNAME: "displayName",
108
108
  PRICEAMOUNT: "priceAmount",
109
+ PRICINGMODEL: "pricingModel",
110
+ PRICELABEL: "priceLabel",
109
111
  PRICECURRENCY: "priceCurrency",
110
112
  BILLINGPERIOD: "billingPeriod",
111
113
  LIMITSJSON: "limitsJson",
@@ -36,6 +36,10 @@ export type ApitogoManifestEnv = keyof typeof APITOGO_MANIFEST_ENV_FILES;
36
36
 
37
37
  const BillingPeriodSchema = z.enum(["month", "year", "monthly", "annual"]);
38
38
 
39
+ export const PlanPricingModelSchema = z.enum(["fixed", "custom"]);
40
+
41
+ export type PlanPricingModel = z.infer<typeof PlanPricingModelSchema>;
42
+
39
43
  const GatewayLimitsSchema = z
40
44
  .object({
41
45
  rateLimit: z
@@ -81,6 +85,9 @@ export const ManifestPlanInputSchema = z.object({
81
85
  sku: z.string().min(1),
82
86
  displayName: z.string().min(1),
83
87
  priceAmount: z.number().default(0),
88
+ /** `custom` plans show a contact-sales form instead of checkout. */
89
+ pricingModel: PlanPricingModelSchema.optional(),
90
+ priceLabel: z.string().max(64).optional(),
84
91
  priceCurrency: PriceCurrencySchema.optional(),
85
92
  billingPeriod: BillingPeriodSchema.optional(),
86
93
  limits: GatewayLimitsSchema.optional(),
@@ -89,6 +96,12 @@ export const ManifestPlanInputSchema = z.object({
89
96
  includedActionKeys: z.array(z.string()).optional(),
90
97
  });
91
98
 
99
+ export function isPlanCustomPriced(plan: {
100
+ pricingModel?: string | null;
101
+ }): boolean {
102
+ return plan.pricingModel === "custom";
103
+ }
104
+
92
105
  /** Free plans have zero price; paid plans have priceAmount greater than zero. */
93
106
  export function isPlanFree(plan: { priceAmount?: number }): boolean {
94
107
  return (plan.priceAmount ?? 0) <= 0;
@@ -198,6 +211,8 @@ export type DevPortalPreviewPlan = {
198
211
  sku: string;
199
212
  displayName: string;
200
213
  priceAmount: number;
214
+ pricingModel: PlanPricingModel;
215
+ priceLabel?: string;
201
216
  priceCurrency: string;
202
217
  billingPeriod: string;
203
218
  limits: GatewayLimits | null;
@@ -272,6 +287,8 @@ export function plansToPreviewCatalog(
272
287
  sku: plan.sku,
273
288
  displayName: plan.displayName,
274
289
  priceAmount: plan.priceAmount ?? 0,
290
+ pricingModel: plan.pricingModel ?? "fixed",
291
+ ...(plan.priceLabel ? { priceLabel: plan.priceLabel } : {}),
275
292
  priceCurrency: plan.priceCurrency ?? "usd",
276
293
  billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
277
294
  limits: coerceGatewayLimits(plan) ?? null,
@@ -264,6 +264,17 @@ const PlanCatalogItemSchema = z.object({
264
264
  sku: z.string(),
265
265
  displayName: z.string(),
266
266
  priceAmount: z.number().default(0),
267
+ pricingModel: z
268
+ .enum(["fixed", "custom"])
269
+ .optional()
270
+ .describe(
271
+ '"custom" shows a contact-sales form instead of checkout; priceAmount must be 0.',
272
+ ),
273
+ priceLabel: z
274
+ .string()
275
+ .max(64)
276
+ .optional()
277
+ .describe('Text shown instead of the price, e.g. "Custom".'),
267
278
  priceCurrency: z
268
279
  .string()
269
280
  .trim()
package/LICENSE.md DELETED
@@ -1,18 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) Zuplo, Inc.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
- associated documentation files (the "Software"), to deal in the Software without restriction,
7
- including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
- sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
9
- furnished to do so, subject to the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be included in all copies or substantial
12
- portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
- NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
17
- OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.