Merge pull request #233 from useautumn/johnyeocx/eng-628-write-body-query-response-zod-schemas

feat: Add OpenAPI schemas for Features, Entities, Core, and Customers (ENG-628)
This commit is contained in:
John Yeo
2025-10-02 14:59:17 +01:00
committed by GitHub
17 changed files with 20737 additions and 196 deletions

View File

@@ -0,0 +1,9 @@
import { z } from "zod/v4";
export const SuccessResponseSchema = z
.object({
success: z.boolean(),
})
.meta({
id: "SuccessResponse",
});

View File

@@ -0,0 +1,182 @@
import { z } from "zod/v4";
// Cancel Schemas
export const CancelBodySchema = z
.object({
customer_id: z.string().meta({
description: "The ID of the customer",
example: "cus_123",
}),
product_id: z.string().meta({
description: "The ID of the product to cancel",
example: "pro_plan",
}),
entity_id: z.string().nullish().meta({
description: "The ID of the entity (optional)",
example: "entity_123",
}),
cancel_immediately: z.boolean().optional().meta({
description: "Whether to cancel the product immediately or at period end",
example: false,
}),
prorate: z.boolean().nullish().meta({
description:
"Whether to prorate the cancellation (defaults to true if not specified)",
example: true,
}),
})
.meta({
id: "CancelBody",
description: "Parameters for canceling a customer's product",
});
export const CancelResultSchema = z
.object({
success: z.boolean().meta({
description: "Whether the cancellation was successful",
example: true,
}),
customer_id: z.string().meta({
description: "The ID of the customer",
example: "cus_123",
}),
product_id: z.string().meta({
description: "The ID of the canceled product",
example: "pro_plan",
}),
})
.meta({
id: "CancelResult",
description: "Result of a product cancellation",
});
// Track Schemas
export const TrackParamsSchema = z
.object({
customer_id: z.string().nonempty().meta({
description: "The ID of the customer",
example: "cus_123",
}),
customer_data: z.any().nullish().meta({
description:
"Customer data to create or update the customer if they don't exist",
example: { name: "John Doe", email: "john@example.com" },
}),
event_name: z.string().nonempty().optional().meta({
description: "The name of the event to track",
example: "api_call",
}),
feature_id: z.string().optional().meta({
description:
"The ID of the feature (alternative to event_name for usage events)",
example: "api_calls",
}),
properties: z.record(z.string(), z.any()).nullish().meta({
description: "Additional properties for the event",
example: { endpoint: "/api/users" },
}),
timestamp: z.number().nullish().meta({
description: "Unix timestamp in milliseconds when the event occurred",
example: 1717000000000,
}),
idempotency_key: z.string().nullish().meta({
description: "Idempotency key to prevent duplicate events",
example: "evt_abc123",
}),
value: z.number().nullish().meta({
description: "The value/count of the event",
example: 1,
}),
set_usage: z.boolean().nullish().meta({
description: "Whether to set the usage to this value instead of increment",
example: false,
}),
entity_id: z.string().nullish().meta({
description: "The ID of the entity this event is associated with",
example: "entity_123",
}),
entity_data: z.any().nullish().meta({
description: "Data for creating the entity if it doesn't exist",
example: { name: "Team Alpha" },
}),
})
.meta({
id: "TrackParams",
description: "Parameters for tracking an event",
});
export const TrackResultSchema = z
.object({
id: z.string().meta({
description: "The ID of the created event",
example: "evt_123",
}),
code: z.string().meta({
description: "Response code",
example: "event_received",
}),
customer_id: z.string().meta({
description: "The ID of the customer",
example: "cus_123",
}),
entity_id: z.string().optional().meta({
description: "The ID of the entity (if provided)",
example: "entity_123",
}),
event_name: z.string().optional().meta({
description: "The name of the event",
example: "api_call",
}),
feature_id: z.string().optional().meta({
description: "The ID of the feature (if provided)",
example: "api_calls",
}),
})
.meta({
id: "TrackResult",
description: "Result of tracking an event",
});
// Query Schemas
export const QueryParamsSchema = z
.object({
customer_id: z.string().meta({
description: "The ID of the customer to query analytics for",
example: "cus_123",
}),
feature_id: z.union([z.string(), z.array(z.string())]).meta({
description: "The feature ID(s) to query",
example: "api_calls",
}),
range: z
.enum(["24h", "7d", "30d", "90d", "last_cycle"] as const)
.nullish()
.meta({
description:
"Time range for the query (defaults to last_cycle if not provided)",
example: "7d",
}),
})
.meta({
id: "QueryParams",
description: "Parameters for querying analytics data",
});
export const QueryResultSchema = z
.object({
list: z.array(z.any()).meta({
description: "List of usage data points",
example: [{ period: 1717000000000, count: 100 }],
}),
})
.meta({
id: "QueryResult",
description: "Result of an analytics query",
});
export type CancelBody = z.infer<typeof CancelBodySchema>;
export type CancelResult = z.infer<typeof CancelResultSchema>;
export type TrackParams = z.infer<typeof TrackParamsSchema>;
export type TrackResult = z.infer<typeof TrackResultSchema>;
export type QueryParams = z.infer<typeof QueryParamsSchema>;
export type QueryResult = z.infer<typeof QueryResultSchema>;

View File

@@ -4,6 +4,14 @@ import {
ExtAttachBodySchema,
ExtCheckoutParamsSchema,
} from "@api/models.js";
import {
CancelBodySchema,
CancelResultSchema,
QueryParamsSchema,
QueryResultSchema,
TrackParamsSchema,
TrackResultSchema,
} from "./coreOpModels.js";
export const coreOps = {
"/core": {
@@ -39,5 +47,56 @@ export const coreOps = {
},
},
},
"/cancel": {
post: {
summary: "Cancel Product",
tags: ["core"],
requestBody: {
content: {
"application/json": { schema: CancelBodySchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: CancelResultSchema } },
},
},
},
},
"/track": {
post: {
summary: "Track Event",
tags: ["core"],
requestBody: {
content: {
"application/json": { schema: TrackParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: TrackResultSchema } },
},
},
},
},
"/query": {
post: {
summary: "Query Analytics",
tags: ["core"],
requestBody: {
content: {
"application/json": { schema: QueryParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: QueryResultSchema } },
},
},
},
},
},
};

View File

@@ -0,0 +1,167 @@
import { z } from "zod/v4";
// Create Customer Params (based on handlePostCustomer logic)
export const CreateCustomerParamsSchema = z
.object({
id: z
.string()
.refine(
(val) => {
if (val === "") return false;
if (val.includes("@")) return false;
if (val.includes(" ")) return false;
if (val.includes(".")) return false;
return /^[a-zA-Z0-9_-]+$/.test(val);
},
{
message:
"ID can only contain letters, numbers, underscores, and hyphens",
},
)
.nullish()
.meta({
description: "Your unique identifier for the customer",
example: "cus_123",
}),
name: z.string().nullish().meta({
description: "Customer's name",
example: "John Doe",
}),
email: z
.string()
.email({ message: "not a valid email address" })
.or(z.literal(""))
.nullish()
.meta({
description: "Customer's email address",
example: "john@example.com",
}),
fingerprint: z.string().nullish().meta({
description:
"Unique identifier (eg, serial number) to detect duplicate customers and prevent free trial abuse",
example: "fp_123abc",
}),
metadata: z.record(z.any(), z.any()).default({}).nullish().meta({
description: "Additional metadata for the customer",
example: { company: "Acme Inc" },
}),
stripe_id: z.string().nullish().meta({
description: "Stripe customer ID if you already have one",
example: "cus_stripe123",
}),
entity_id: z.string().nullish().meta({
description: "Entity ID to associate with the customer",
example: "entity_123",
}),
entity_data: z.any().nullish().meta({
description: "Data for creating an entity",
example: { name: "Team Alpha", feature_id: "seats" },
}),
})
.meta({
id: "CreateCustomerParams",
description: "Parameters for creating a customer",
});
// Update Customer Params (based on handleUpdateCustomer logic)
export const UpdateCustomerParamsSchema = z
.object({
id: z
.string()
.refine(
(val) => {
if (val === "") return false;
if (val.includes("@")) return false;
if (val.includes(" ")) return false;
if (val.includes(".")) return false;
return /^[a-zA-Z0-9_-]+$/.test(val);
},
{
message:
"ID can only contain letters, numbers, underscores, and hyphens",
},
)
.nullish()
.meta({
description:
"New unique identifier for the customer (cannot be changed to null)",
example: "cus_123",
}),
name: z.string().nullish().meta({
description: "Customer's name",
example: "John Doe",
}),
email: z
.string()
.email({ message: "not a valid email address" })
.or(z.literal(""))
.nullish()
.meta({
description: "Customer's email address",
example: "john@example.com",
}),
fingerprint: z.string().nullish().meta({
description:
"Unique identifier (eg, serial number) to detect duplicate customers",
example: "fp_123abc",
}),
metadata: z.record(z.any(), z.any()).nullish().meta({
description:
"Additional metadata for the customer (set individual keys to null to delete them)",
example: { company: "Acme Inc" },
}),
stripe_id: z.string().nullish().meta({
description: "Stripe customer ID",
example: "cus_stripe123",
}),
})
.meta({
id: "UpdateCustomerParams",
description: "Parameters for updating a customer",
});
// List Customers Query (based on the docs)
export const ListCustomersQuerySchema = z
.object({
limit: z.number().int().min(10).max(100).default(10).optional().meta({
description: "Maximum number of customers to return",
example: 10,
}),
offset: z.number().int().min(0).default(0).optional().meta({
description: "Number of customers to skip before returning results",
example: 0,
}),
})
.meta({
id: "ListCustomersQuery",
description: "Query parameters for listing customers",
});
// List Customers Response
export const ListCustomersResponseSchema = z
.object({
list: z.array(z.any()).meta({
description: "List of customers",
}),
total: z.number().int().meta({
description: "Total number of customers available",
example: 100,
}),
limit: z.number().int().meta({
description: "Maximum number of customers returned",
example: 10,
}),
offset: z.number().int().meta({
description: "Number of customers skipped before returning results",
example: 0,
}),
})
.meta({
id: "ListCustomersResponse",
description: "Response for listing customers",
});
export type CreateCustomerParams = z.infer<typeof CreateCustomerParamsSchema>;
export type UpdateCustomerParams = z.infer<typeof UpdateCustomerParamsSchema>;
export type ListCustomersQuery = z.infer<typeof ListCustomersQuerySchema>;
export type ListCustomersResponse = z.infer<typeof ListCustomersResponseSchema>;

View File

@@ -0,0 +1,113 @@
import { z } from "zod/v4";
import { APICustomerSchema } from "./apiCustomer.js";
import {
CreateCustomerParamsSchema,
ListCustomersResponseSchema,
UpdateCustomerParamsSchema,
} from "./customerOpModels.js";
import { SuccessResponseSchema } from "../common/commonResponses.js";
export const customerOps = {
"/customers": {
get: {
summary: "List Customers",
tags: ["customers"],
requestParams: {
query: z.object({
limit: z.number().int().min(10).max(100).optional(),
offset: z.number().int().min(0).optional(),
}),
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": { schema: ListCustomersResponseSchema },
},
},
},
},
post: {
summary: "Create Customer",
tags: ["customers"],
requestParams: {
query: z.object({
expand: z.string().optional(),
}),
},
requestBody: {
content: {
"application/json": { schema: CreateCustomerParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APICustomerSchema } },
},
},
},
},
"/customers/{customer_id}": {
get: {
summary: "Get Customer",
tags: ["customers"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
query: z.object({
expand: z.string().optional(),
}),
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APICustomerSchema } },
},
},
},
post: {
summary: "Update Customer",
tags: ["customers"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
query: z.object({
expand: z.string().optional(),
}),
},
requestBody: {
content: {
"application/json": { schema: UpdateCustomerParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APICustomerSchema } },
},
},
},
delete: {
summary: "Delete Customer",
tags: ["customers"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": {
schema: SuccessResponseSchema,
},
},
},
},
},
},
};

View File

@@ -0,0 +1,5 @@
import { EntityResponseSchema } from "@models/cusModels/entityModels/entityResModels.js";
import type { z } from "zod/v4";
export const APIEntitySchema = EntityResponseSchema;
export type APIEntity = z.infer<typeof APIEntitySchema>;

View File

@@ -0,0 +1,100 @@
import { z } from "zod/v4";
import { APIEntitySchema } from "./apiEntity.js";
import { CreateEntityParamsSchema } from "./entityOpModels.js";
import { SuccessResponseSchema } from "../common/commonResponses.js";
const EntityListResponseSchema = z
.object({
data: z.array(APIEntitySchema),
})
.meta({
id: "EntityListResponse",
});
export const entityOps = {
"/customers/{customer_id}/entities": {
get: {
summary: "List Entities",
tags: ["entities"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
query: z.object({
expand: z.string().optional(),
}),
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": {
schema: EntityListResponseSchema,
},
},
},
},
},
post: {
summary: "Create Entity",
tags: ["entities"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
},
requestBody: {
content: {
"application/json": { schema: CreateEntityParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APIEntitySchema } },
},
},
},
},
"/customers/{customer_id}/entities/{entity_id}": {
get: {
summary: "Get Entity",
tags: ["entities"],
requestParams: {
path: z.object({
customer_id: z.string(),
entity_id: z.string(),
}),
query: z.object({
expand: z.string().optional(),
}),
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APIEntitySchema } },
},
},
},
delete: {
summary: "Delete Entity",
tags: ["entities"],
requestParams: {
path: z.object({
customer_id: z.string(),
entity_id: z.string(),
}),
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": {
schema: SuccessResponseSchema,
},
},
},
},
},
},
};

View File

@@ -0,0 +1,39 @@
import { z } from "zod/v4";
// Create Entity Params (based on CreateEntitySchema from shared/models)
export const CreateEntityParamsSchema = z
.object({
id: z.string().meta({
description: "The ID of the entity",
example: "entity_123",
}),
name: z.string().nullish().meta({
description: "The name of the entity",
example: "Team Alpha",
}),
feature_id: z.string().meta({
description: "The ID of the feature this entity is associated with",
example: "seats",
}),
})
.meta({
id: "CreateEntityParams",
description: "Parameters for creating an entity",
});
// Get Entity Query Params
export const GetEntityQuerySchema = z
.object({
expand: z.string().optional().meta({
description:
"Comma-separated list of fields to expand (e.g., 'invoices')",
example: "invoices",
}),
})
.meta({
id: "GetEntityQuery",
description: "Query parameters for getting an entity",
});
export type CreateEntityParams = z.infer<typeof CreateEntityParamsSchema>;
export type GetEntityQuery = z.infer<typeof GetEntityQuerySchema>;

View File

@@ -10,7 +10,7 @@ export enum APIFeatureType {
export const APIFeatureSchema = z.object({
id: z.string(),
name: z.string().nullish(),
type: z.enum(APIFeatureType),
type: z.nativeEnum(APIFeatureType),
display: z
.object({
singular: z.string(),

View File

@@ -0,0 +1,97 @@
import { APIFeatureSchema, APIFeatureType } from "./apiFeature.js";
import { z } from "zod/v4";
// Create Feature Params
export const CreateFeatureParamsSchema = z
.object({
id: z.string().meta({
description: "The ID of the feature",
example: "feature_123",
}),
name: z.string().nullish().meta({
description: "The name of the feature",
example: "API Calls",
}),
type: z.nativeEnum(APIFeatureType).meta({
description: "The type of the feature",
example: "single_use",
}),
display: z
.object({
singular: z.string(),
plural: z.string(),
})
.nullish()
.meta({
description: "Display names for the feature",
example: { singular: "API Call", plural: "API Calls" },
}),
credit_schema: z
.array(
z.object({
metered_feature_id: z.string(),
credit_cost: z.number(),
}),
)
.nullish()
.meta({
description:
"Credit schema for credit system features (only applicable when type is credit_system)",
example: [{ metered_feature_id: "api_calls", credit_cost: 10 }],
}),
})
.meta({
id: "CreateFeatureParams",
description: "Parameters for creating a feature",
});
// Update Feature Params
export const UpdateFeatureParamsSchema = z
.object({
id: z.string().optional().meta({
description: "The ID of the feature",
example: "feature_123",
}),
name: z.string().nullish().meta({
description: "The name of the feature",
example: "API Calls",
}),
type: z.nativeEnum(APIFeatureType).optional().meta({
description: "The type of the feature",
example: "single_use",
}),
display: z
.object({
singular: z.string(),
plural: z.string(),
})
.nullish()
.meta({
description: "Display names for the feature",
example: { singular: "API Call", plural: "API Calls" },
}),
credit_schema: z
.array(
z.object({
metered_feature_id: z.string(),
credit_cost: z.number(),
}),
)
.nullish()
.meta({
description:
"Credit schema for credit system features (only applicable when type is credit_system)",
example: [{ metered_feature_id: "api_calls", credit_cost: 10 }],
}),
archived: z.boolean().nullish().meta({
description: "Whether the feature is archived",
example: false,
}),
})
.meta({
id: "UpdateFeatureParams",
description: "Parameters for updating a feature",
});
export type CreateFeatureParams = z.infer<typeof CreateFeatureParamsSchema>;
export type UpdateFeatureParams = z.infer<typeof UpdateFeatureParamsSchema>;

View File

@@ -0,0 +1,106 @@
import { APIFeatureSchema } from "./apiFeature.js";
import {
CreateFeatureParamsSchema,
UpdateFeatureParamsSchema,
} from "./featureOpModels.js";
import { z } from "zod/v4";
import { SuccessResponseSchema } from "../common/commonResponses.js";
const FeatureListResponseSchema = z
.object({
list: z.array(APIFeatureSchema),
})
.meta({
id: "FeatureListResponse",
});
export const featureOps = {
"/features": {
get: {
summary: "List Features",
tags: ["features"],
requestParams: {
query: z.object({
include_archived: z.boolean().optional(),
}),
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: FeatureListResponseSchema } },
},
},
},
post: {
summary: "Create Feature",
tags: ["features"],
requestBody: {
content: {
"application/json": { schema: CreateFeatureParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APIFeatureSchema } },
},
},
},
},
"/features/{featureId}": {
get: {
summary: "Get Feature",
tags: ["features"],
requestParams: {
path: z.object({
featureId: z.string(),
}),
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APIFeatureSchema } },
},
},
},
post: {
summary: "Update Feature",
tags: ["features"],
requestParams: {
path: z.object({
featureId: z.string(),
}),
},
requestBody: {
content: {
"application/json": { schema: UpdateFeatureParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: APIFeatureSchema } },
},
},
},
delete: {
summary: "Delete Feature",
tags: ["features"],
requestParams: {
path: z.object({
featureId: z.string(),
}),
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": {
schema: SuccessResponseSchema,
},
},
},
},
},
},
};

View File

@@ -1,6 +1,8 @@
// Core
export * from "./core/attachModels.js";
export * from "./core/checkoutModels.js";
export * from "./core/coreOpModels.js";
export * from "./core/coreOpenApi.js";
// Customers
@@ -8,17 +10,29 @@ export * from "./customers/apiCustomer.js";
export * from "./customers/components/apiCusFeature.js";
export * from "./customers/components/apiCusProduct.js";
export * from "./customers/components/apiCusReferral.js";
export * from "./customers/customerOpModels.js";
export * from "./customers/customersOpenApi.js";
// Entities
export * from "./entities/apiEntity.js";
export * from "./entities/entityOpModels.js";
export * from "./entities/entitiesOpenApi.js";
// Features
export * from "./features/apiFeature.js";
export * from "./features/featureOpModels.js";
export * from "./features/featuresOpenApi.js";
// Others
export * from "./others/apiDiscount.js";
export * from "./others/apiInvoice.js";
// Product
export * from "./products/apiFreeTrial.js";
export * from "./products/apiProduct.js";
export * from "./products/apiProductItem.js";
export * from "./products/productOpModels.js";
export * from "./products/productsOpenApi.js";
// export * from "./products/apiFreeTrial.js";
// export * from "./products/apiProduct.js";

View File

@@ -4,6 +4,9 @@ import yaml from "yaml";
import { z } from "zod/v4";
import { createDocument } from "zod-openapi";
import { coreOps } from "./core/coreOpenApi.js";
import { customerOps } from "./customers/customersOpenApi.js";
import { entityOps } from "./entities/entitiesOpenApi.js";
import { featureOps } from "./features/featuresOpenApi.js";
import { productOps } from "./products/productsOpenApi.js";
const API_VERSION = "1.2.0";
@@ -33,7 +36,7 @@ const document = createDocument({
.object({
message: z.string(),
code: z.string(),
env: z.enum(AppEnv),
env: z.nativeEnum(AppEnv),
})
.meta({
id: "AutumnError",
@@ -52,15 +55,24 @@ const document = createDocument({
paths: {
...productOps,
...coreOps,
...featureOps,
...customerOps,
...entityOps,
},
});
// Export to YAML file during build
if (process.env.NODE_ENV !== "production") {
try {
const yamlContent = yaml.stringify(document);
writeFileSync("./openapi.yaml", yamlContent, "utf8");
console.log("OpenAPI document exported to openapi-customer.yaml");
// Export as JSON (YAML export has issues with zod schemas)
const jsonStr = JSON.stringify(document, null, 2);
writeFileSync("./openapi.json", jsonStr, "utf8");
console.log("OpenAPI document exported to openapi.json");
// TODO: Fix YAML export - currently fails with "Tag not resolved for Function value"
// const yamlContent = yaml.stringify(document);
// writeFileSync("./openapi.yaml", yamlContent, "utf8");
// console.log("OpenAPI document exported to openapi.yaml");
} catch (error) {
console.error("Failed to export OpenAPI document:", error);
}

View File

@@ -1 +1 @@
export * from "./features/updateFeatureParams.js";
// export * from "./features/updateFeatureParams.js";

View File

@@ -41,6 +41,7 @@ export const UpdateProductV2ParamsSchema = CreateProductV2ParamsSchema.extend({
is_default: z.boolean().optional(),
version: z.number().optional(),
group: z.string().optional(),
archived: z.boolean().optional(),
// items: z.array(CreateProductItemParamsSchema).optional(),
free_trial: CreateFreeTrialSchema.nullish(),

19426
shared/openapi.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,196 +17,23 @@ paths:
content:
application/json:
schema:
type: object
properties:
id:
type: string
minLength: 1
pattern: ^[a-zA-Z0-9_-]+$
name:
type: string
is_add_on:
default: false
type: boolean
is_default:
default: false
type: boolean
version:
type: number
group:
default: ""
type: string
items:
type: array
items:
type: object
properties:
feature_id:
anyOf:
- type: string
- type: "null"
feature_type:
anyOf:
- type: string
enum:
- single_use
- continuous_use
- boolean
- static
- type: "null"
included_usage:
anyOf:
- anyOf:
- type: number
- type: string
const: inf
- type: "null"
interval:
anyOf:
- type: string
enum:
- minute
- hour
- day
- week
- month
- quarter
- semi_annual
- year
- type: "null"
interval_count:
anyOf:
- type: number
- type: "null"
entity_feature_id:
anyOf:
- type: string
- type: "null"
usage_model:
anyOf:
- type: string
enum:
- prepaid
- pay_per_use
- type: "null"
price:
anyOf:
- type: number
- type: "null"
tiers:
anyOf:
- type: array
items:
type: object
properties:
to:
anyOf:
- type: number
- type: string
const: inf
amount:
type: number
required:
- to
- amount
- type: "null"
billing_units:
anyOf:
- type: number
- type: "null"
usage_limit:
anyOf:
- type: number
- type: "null"
reset_usage_when_enabled:
anyOf:
- type: boolean
- type: "null"
config:
anyOf:
- type: object
properties:
on_increase:
anyOf:
- type: string
enum:
- bill_immediately
- prorate_immediately
- prorate_next_cycle
- bill_next_cycle
- type: "null"
on_decrease:
anyOf:
- type: string
enum:
- prorate
- prorate_immediately
- prorate_next_cycle
- none
- no_prorations
- type: "null"
rollover:
anyOf:
- type: object
properties:
max:
anyOf:
- type: number
- type: "null"
duration:
default: month
type: string
enum:
- month
- forever
length:
type: number
required:
- max
- length
- type: "null"
- type: "null"
created_at:
anyOf:
- type: number
- type: "null"
entitlement_id:
anyOf:
- type: string
- type: "null"
price_id:
anyOf:
- type: string
- type: "null"
price_config:
anyOf:
- {}
- type: "null"
free_trial:
type: object
properties:
length:
anyOf:
- type: string
- type: number
unique_fingerprint:
default: false
type: boolean
duration:
default: day
type: string
enum:
- day
- month
- year
card_required:
default: true
type: boolean
required:
- length
required:
- id
- name
$ref: "#/components/schemas/CreateProductParams"
responses:
"200":
description: 200 OK
content:
application/json:
schema:
$ref: "#/components/schemas/Product"
patch:
summary: Update Product
tags:
- products
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateProductParams"
responses:
"200":
description: 200 OK
@@ -216,6 +43,390 @@ paths:
$ref: "#/components/schemas/Product"
components:
schemas:
CreateProductParams:
description: Create Product
type: object
properties:
id:
type: string
minLength: 1
pattern: ^[a-zA-Z0-9_-]+$
name:
type: string
is_add_on:
default: false
type: boolean
is_default:
default: false
type: boolean
version:
type: number
group:
default: ""
type: string
items:
type: array
items:
type: object
properties:
feature_id:
anyOf:
- type: string
- type: "null"
feature_type:
anyOf:
- type: string
enum:
- single_use
- continuous_use
- boolean
- static
- type: "null"
included_usage:
anyOf:
- anyOf:
- type: number
- type: string
const: inf
- type: "null"
interval:
anyOf:
- type: string
enum:
- minute
- hour
- day
- week
- month
- quarter
- semi_annual
- year
- type: "null"
interval_count:
anyOf:
- type: number
- type: "null"
entity_feature_id:
anyOf:
- type: string
- type: "null"
usage_model:
anyOf:
- type: string
enum:
- prepaid
- pay_per_use
- type: "null"
price:
anyOf:
- type: number
- type: "null"
tiers:
anyOf:
- type: array
items:
type: object
properties:
to:
anyOf:
- type: number
- type: string
const: inf
amount:
type: number
required:
- to
- amount
- type: "null"
billing_units:
anyOf:
- type: number
- type: "null"
usage_limit:
anyOf:
- type: number
- type: "null"
reset_usage_when_enabled:
anyOf:
- type: boolean
- type: "null"
config:
anyOf:
- type: object
properties:
on_increase:
anyOf:
- type: string
enum:
- bill_immediately
- prorate_immediately
- prorate_next_cycle
- bill_next_cycle
- type: "null"
on_decrease:
anyOf:
- type: string
enum:
- prorate
- prorate_immediately
- prorate_next_cycle
- none
- no_prorations
- type: "null"
rollover:
anyOf:
- type: object
properties:
max:
anyOf:
- type: number
- type: "null"
duration:
default: month
type: string
enum:
- month
- forever
length:
type: number
required:
- max
- length
- type: "null"
- type: "null"
created_at:
anyOf:
- type: number
- type: "null"
entitlement_id:
anyOf:
- type: string
- type: "null"
price_id:
anyOf:
- type: string
- type: "null"
price_config:
anyOf:
- {}
- type: "null"
free_trial:
anyOf:
- type: object
properties:
length:
anyOf:
- type: string
- type: number
unique_fingerprint:
default: false
type: boolean
duration:
default: day
type: string
enum:
- day
- month
- year
card_required:
default: true
type: boolean
required:
- length
- type: "null"
required:
- id
- name
UpdateProductParams:
description: Update Product
type: object
properties:
id:
type: string
minLength: 1
pattern: ^[a-zA-Z0-9_-]+$
name:
type: string
is_add_on:
type: boolean
is_default:
type: boolean
version:
type: number
group:
type: string
items:
type: array
items:
type: object
properties:
feature_id:
anyOf:
- type: string
- type: "null"
feature_type:
anyOf:
- type: string
enum:
- single_use
- continuous_use
- boolean
- static
- type: "null"
included_usage:
anyOf:
- anyOf:
- type: number
- type: string
const: inf
- type: "null"
interval:
anyOf:
- type: string
enum:
- minute
- hour
- day
- week
- month
- quarter
- semi_annual
- year
- type: "null"
interval_count:
anyOf:
- type: number
- type: "null"
entity_feature_id:
anyOf:
- type: string
- type: "null"
usage_model:
anyOf:
- type: string
enum:
- prepaid
- pay_per_use
- type: "null"
price:
anyOf:
- type: number
- type: "null"
tiers:
anyOf:
- type: array
items:
type: object
properties:
to:
anyOf:
- type: number
- type: string
const: inf
amount:
type: number
required:
- to
- amount
- type: "null"
billing_units:
anyOf:
- type: number
- type: "null"
usage_limit:
anyOf:
- type: number
- type: "null"
reset_usage_when_enabled:
anyOf:
- type: boolean
- type: "null"
config:
anyOf:
- type: object
properties:
on_increase:
anyOf:
- type: string
enum:
- bill_immediately
- prorate_immediately
- prorate_next_cycle
- bill_next_cycle
- type: "null"
on_decrease:
anyOf:
- type: string
enum:
- prorate
- prorate_immediately
- prorate_next_cycle
- none
- no_prorations
- type: "null"
rollover:
anyOf:
- type: object
properties:
max:
anyOf:
- type: number
- type: "null"
duration:
default: month
type: string
enum:
- month
- forever
length:
type: number
required:
- max
- length
- type: "null"
- type: "null"
created_at:
anyOf:
- type: number
- type: "null"
entitlement_id:
anyOf:
- type: string
- type: "null"
price_id:
anyOf:
- type: string
- type: "null"
price_config:
anyOf:
- {}
- type: "null"
free_trial:
anyOf:
- type: object
properties:
length:
anyOf:
- type: string
- type: number
unique_fingerprint:
default: false
type: boolean
duration:
default: day
type: string
enum:
- day
- month
- year
card_required:
default: true
type: boolean
required:
- length
- type: "null"
archived:
type: boolean
Product:
description: A product
type: object