> For the complete documentation index, see [llms.txt](https://developers.share.inc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.share.inc/api-reference/plans.md).

# Plans

CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.

## List plans

> Returns the paginated set of service plans owned by the authenticated provider.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"ApiResponseDto":{"properties":{},"type":"object"},"PlanResponseDto":{"properties":{"billingCycleDays":{"description":"Length of one billing period in days (1, 7, 30, 90, or 365)","type":"number"},"createdAt":{"format":"date-time","type":"string"},"dataCapMb":{"description":"Monthly data allowance in MB (null = unlimited)","nullable":true,"type":"number"},"disabledAt":{"description":"Timestamp when the plan was disabled (null = active). Disabled plans are retained but cannot be assigned to new subscriptions; cohort 1 subs continue until the next renewal cycle.","format":"date-time","nullable":true,"type":"string"},"downloadSpeedMbps":{"description":"Download speed in Mbps","type":"number"},"effectiveDate":{"description":"When the package takes effect","format":"date-time","nullable":true,"type":"string"},"externalPackageId":{"description":"Package ID in the partner's platform","nullable":true,"type":"string"},"gracePeriodDays":{"description":"Grace period in days after overdue before suspension (0-30)","nullable":true,"type":"number"},"id":{"description":"Share internal identifier","format":"uuid","type":"string"},"installationFee":{"description":"One-time installation fee","type":"number"},"isActive":{"type":"boolean"},"planName":{"type":"string"},"planType":{"enum":["subscriber","hotspot"],"type":"string"},"priceAmount":{"description":"Cost per billing cycle","type":"number"},"priceCurrency":{"description":"ISO 4217 currency code","type":"string"},"radiusGroupName":{"description":"FreeRADIUS group name (generated by the backend, read-only)","nullable":true,"type":"string"},"speed":{"description":"Legacy speed field (mirrors download Mbps for backwards compatibility)","type":"number"},"updatedAt":{"format":"date-time","type":"string"},"uploadSpeedMbps":{"description":"Upload speed in Mbps","type":"number"}},"required":["id","externalPackageId","planType","planName","downloadSpeedMbps","uploadSpeedMbps","speed","priceAmount","priceCurrency","billingCycleDays","installationFee","isActive","createdAt","updatedAt"],"type":"object"}}},"paths":{"/v1/plans":{"get":{"description":"Returns the paginated set of service plans owned by the authenticated provider.","operationId":"PlansController_listPlans_v1","parameters":[{"description":"1-based page number. Defaults to 1 when omitted.","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"number"}},{"description":"Number of items per page. Capped at 100 server-side. Defaults to 20 when omitted.","in":"query","name":"limit","required":false,"schema":{"default":20,"maximum":100,"minimum":1,"type":"number"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/PlanResponseDto"},"type":"array"},"success":{"type":"boolean"}}}]}}},"description":"Paginated list of plans."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"List plans","tags":["Plans"]}}}}
```

## Create a plan

> Creates a new service plan under the authenticated provider. The plan type, currency, speed, and price are validated up-front: only \`RESIDENTIAL\` plans are creatable via the public API; speed must be ≤ 1 Gbps; price must clear the documented price floor for the plan type and currency.\
> \
> \*\*Error codes:\*\* \`PLAN\_TYPE\_NOT\_SUPPORTED\_VIA\_API\` (400), \`PLAN\_CURRENCY\_NOT\_SUPPORTED\` (400), \`PLAN\_SPEED\_EXCEEDS\_1GBPS\` (400), \`PLAN\_PRICE\_BELOW\_FLOOR\` (400). See the \[Error codes]\(../errors/error-codes.md) reference.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"CreatePlanRequestDto":{"properties":{"billingCycleDays":{"description":"Length of one billing period in days","enum":[1,7,30,90,365],"type":"number"},"dataCapMb":{"description":"Monthly data allowance in MB (null = unlimited)","nullable":true,"type":"number"},"downloadSpeedMbps":{"description":"Download speed in Mbps","type":"number"},"effectiveDate":{"description":"When the package takes effect (defaults to now)","format":"date-time","type":"string"},"externalPackageId":{"description":"Package ID in the partner's platform","type":"string"},"gracePeriodDays":{"description":"Grace period in days after overdue before suspension (0-30)","type":"number"},"installationFee":{"description":"One-time installation fee","type":"number"},"planName":{"type":"string"},"planType":{"enum":["subscriber","hotspot"],"type":"string"},"priceAmount":{"description":"Cost per billing cycle","type":"number"},"priceCurrency":{"description":"ISO 4217 currency code","type":"string"},"uploadSpeedMbps":{"description":"Upload speed in Mbps","type":"number"}},"required":["planType","planName","downloadSpeedMbps","uploadSpeedMbps","priceAmount","priceCurrency","billingCycleDays"],"type":"object"},"ApiResponseDto":{"properties":{},"type":"object"},"PlanResponseDto":{"properties":{"billingCycleDays":{"description":"Length of one billing period in days (1, 7, 30, 90, or 365)","type":"number"},"createdAt":{"format":"date-time","type":"string"},"dataCapMb":{"description":"Monthly data allowance in MB (null = unlimited)","nullable":true,"type":"number"},"disabledAt":{"description":"Timestamp when the plan was disabled (null = active). Disabled plans are retained but cannot be assigned to new subscriptions; cohort 1 subs continue until the next renewal cycle.","format":"date-time","nullable":true,"type":"string"},"downloadSpeedMbps":{"description":"Download speed in Mbps","type":"number"},"effectiveDate":{"description":"When the package takes effect","format":"date-time","nullable":true,"type":"string"},"externalPackageId":{"description":"Package ID in the partner's platform","nullable":true,"type":"string"},"gracePeriodDays":{"description":"Grace period in days after overdue before suspension (0-30)","nullable":true,"type":"number"},"id":{"description":"Share internal identifier","format":"uuid","type":"string"},"installationFee":{"description":"One-time installation fee","type":"number"},"isActive":{"type":"boolean"},"planName":{"type":"string"},"planType":{"enum":["subscriber","hotspot"],"type":"string"},"priceAmount":{"description":"Cost per billing cycle","type":"number"},"priceCurrency":{"description":"ISO 4217 currency code","type":"string"},"radiusGroupName":{"description":"FreeRADIUS group name (generated by the backend, read-only)","nullable":true,"type":"string"},"speed":{"description":"Legacy speed field (mirrors download Mbps for backwards compatibility)","type":"number"},"updatedAt":{"format":"date-time","type":"string"},"uploadSpeedMbps":{"description":"Upload speed in Mbps","type":"number"}},"required":["id","externalPackageId","planType","planName","downloadSpeedMbps","uploadSpeedMbps","speed","priceAmount","priceCurrency","billingCycleDays","installationFee","isActive","createdAt","updatedAt"],"type":"object"}}},"paths":{"/v1/plans":{"post":{"description":"Creates a new service plan under the authenticated provider. The plan type, currency, speed, and price are validated up-front: only `RESIDENTIAL` plans are creatable via the public API; speed must be ≤ 1 Gbps; price must clear the documented price floor for the plan type and currency.\n\n**Error codes:** `PLAN_TYPE_NOT_SUPPORTED_VIA_API` (400), `PLAN_CURRENCY_NOT_SUPPORTED` (400), `PLAN_SPEED_EXCEEDS_1GBPS` (400), `PLAN_PRICE_BELOW_FLOOR` (400). See the [Error codes](../errors/error-codes.md) reference.","operationId":"PlansController_createPlan_v1","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlanRequestDto"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/PlanResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"The newly created plan."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Create a plan","tags":["Plans"]}}}}
```

## Duplicate a plan

> Clones an existing plan as a starting point for a new offering. Optional \`body.name\` overrides the new plan’s name; when omitted it defaults to \`"\<original planName> (copy)"\`. The new plan starts inactive so it can be reviewed and edited before being exposed to subscribers. \`externalPackageId\` is not cloned — the duplicate receives its own external mapping on the next sync. The clone is re-validated against the same rules as \`POST /v1/plans\`, so a plan provisioned outside this surface that violates them cannot be replicated through it.\
> \
> \*\*Error codes:\*\* \`PLAN\_TYPE\_NOT\_SUPPORTED\_VIA\_API\` (400), \`PLAN\_CURRENCY\_NOT\_SUPPORTED\` (400), \`PLAN\_SPEED\_EXCEEDS\_1GBPS\` (400), \`PLAN\_PRICE\_BELOW\_FLOOR\` (400). See the \[Error codes]\(../errors/error-codes.md) reference.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"DuplicatePlanRequestDto":{"properties":{"name":{"description":"Optional override for the new plan’s name. When omitted, the new plan is named `\"<original planName> (copy)\"`.","type":"string"}},"type":"object"},"ApiResponseDto":{"properties":{},"type":"object"},"PlanResponseDto":{"properties":{"billingCycleDays":{"description":"Length of one billing period in days (1, 7, 30, 90, or 365)","type":"number"},"createdAt":{"format":"date-time","type":"string"},"dataCapMb":{"description":"Monthly data allowance in MB (null = unlimited)","nullable":true,"type":"number"},"disabledAt":{"description":"Timestamp when the plan was disabled (null = active). Disabled plans are retained but cannot be assigned to new subscriptions; cohort 1 subs continue until the next renewal cycle.","format":"date-time","nullable":true,"type":"string"},"downloadSpeedMbps":{"description":"Download speed in Mbps","type":"number"},"effectiveDate":{"description":"When the package takes effect","format":"date-time","nullable":true,"type":"string"},"externalPackageId":{"description":"Package ID in the partner's platform","nullable":true,"type":"string"},"gracePeriodDays":{"description":"Grace period in days after overdue before suspension (0-30)","nullable":true,"type":"number"},"id":{"description":"Share internal identifier","format":"uuid","type":"string"},"installationFee":{"description":"One-time installation fee","type":"number"},"isActive":{"type":"boolean"},"planName":{"type":"string"},"planType":{"enum":["subscriber","hotspot"],"type":"string"},"priceAmount":{"description":"Cost per billing cycle","type":"number"},"priceCurrency":{"description":"ISO 4217 currency code","type":"string"},"radiusGroupName":{"description":"FreeRADIUS group name (generated by the backend, read-only)","nullable":true,"type":"string"},"speed":{"description":"Legacy speed field (mirrors download Mbps for backwards compatibility)","type":"number"},"updatedAt":{"format":"date-time","type":"string"},"uploadSpeedMbps":{"description":"Upload speed in Mbps","type":"number"}},"required":["id","externalPackageId","planType","planName","downloadSpeedMbps","uploadSpeedMbps","speed","priceAmount","priceCurrency","billingCycleDays","installationFee","isActive","createdAt","updatedAt"],"type":"object"}}},"paths":{"/v1/plans/duplicate/{id}":{"post":{"description":"Clones an existing plan as a starting point for a new offering. Optional `body.name` overrides the new plan’s name; when omitted it defaults to `\"<original planName> (copy)\"`. The new plan starts inactive so it can be reviewed and edited before being exposed to subscribers. `externalPackageId` is not cloned — the duplicate receives its own external mapping on the next sync. The clone is re-validated against the same rules as `POST /v1/plans`, so a plan provisioned outside this surface that violates them cannot be replicated through it.\n\n**Error codes:** `PLAN_TYPE_NOT_SUPPORTED_VIA_API` (400), `PLAN_CURRENCY_NOT_SUPPORTED` (400), `PLAN_SPEED_EXCEEDS_1GBPS` (400), `PLAN_PRICE_BELOW_FLOOR` (400). See the [Error codes](../errors/error-codes.md) reference.","operationId":"PlansController_duplicatePlan_v1","parameters":[{"description":"Source plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicatePlanRequestDto"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/PlanResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"The newly created duplicate plan."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Duplicate a plan","tags":["Plans"]}}}}
```

## Get a plan

> Returns a single plan owned by the authenticated provider.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"ApiResponseDto":{"properties":{},"type":"object"},"PlanResponseDto":{"properties":{"billingCycleDays":{"description":"Length of one billing period in days (1, 7, 30, 90, or 365)","type":"number"},"createdAt":{"format":"date-time","type":"string"},"dataCapMb":{"description":"Monthly data allowance in MB (null = unlimited)","nullable":true,"type":"number"},"disabledAt":{"description":"Timestamp when the plan was disabled (null = active). Disabled plans are retained but cannot be assigned to new subscriptions; cohort 1 subs continue until the next renewal cycle.","format":"date-time","nullable":true,"type":"string"},"downloadSpeedMbps":{"description":"Download speed in Mbps","type":"number"},"effectiveDate":{"description":"When the package takes effect","format":"date-time","nullable":true,"type":"string"},"externalPackageId":{"description":"Package ID in the partner's platform","nullable":true,"type":"string"},"gracePeriodDays":{"description":"Grace period in days after overdue before suspension (0-30)","nullable":true,"type":"number"},"id":{"description":"Share internal identifier","format":"uuid","type":"string"},"installationFee":{"description":"One-time installation fee","type":"number"},"isActive":{"type":"boolean"},"planName":{"type":"string"},"planType":{"enum":["subscriber","hotspot"],"type":"string"},"priceAmount":{"description":"Cost per billing cycle","type":"number"},"priceCurrency":{"description":"ISO 4217 currency code","type":"string"},"radiusGroupName":{"description":"FreeRADIUS group name (generated by the backend, read-only)","nullable":true,"type":"string"},"speed":{"description":"Legacy speed field (mirrors download Mbps for backwards compatibility)","type":"number"},"updatedAt":{"format":"date-time","type":"string"},"uploadSpeedMbps":{"description":"Upload speed in Mbps","type":"number"}},"required":["id","externalPackageId","planType","planName","downloadSpeedMbps","uploadSpeedMbps","speed","priceAmount","priceCurrency","billingCycleDays","installationFee","isActive","createdAt","updatedAt"],"type":"object"}}},"paths":{"/v1/plans/{id}":{"get":{"description":"Returns a single plan owned by the authenticated provider.","operationId":"PlansController_getPlan_v1","parameters":[{"description":"Plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/PlanResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"The requested plan."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Get a plan","tags":["Plans"]}}}}
```

## Update a plan

> Updates the writable fields of an existing plan. The same up-front validation as create runs against the resolved fields (sent ∪ existing).\
> \
> \*\*Error codes:\*\* \`PLAN\_TYPE\_NOT\_SUPPORTED\_VIA\_API\` (400), \`PLAN\_CURRENCY\_NOT\_SUPPORTED\` (400), \`PLAN\_SPEED\_EXCEEDS\_1GBPS\` (400), \`PLAN\_PRICE\_BELOW\_FLOOR\` (400). See the \[Error codes]\(../errors/error-codes.md) reference.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"UpdatePlanRequestDto":{"properties":{"billingCycleDays":{"description":"Length of one billing period in days","enum":[1,7,30,90,365],"type":"number"},"dataCapMb":{"description":"Monthly data allowance in MB (null = unlimited)","nullable":true,"type":"number"},"downloadSpeedMbps":{"description":"Download speed in Mbps","type":"number"},"effectiveDate":{"description":"When the package takes effect","format":"date-time","type":"string"},"externalPackageId":{"description":"Package ID in the partner's platform","nullable":true,"type":"string"},"gracePeriodDays":{"description":"Grace period in days after overdue before suspension (0-30)","type":"number"},"installationFee":{"description":"One-time installation fee","type":"number"},"isActive":{"description":"Activate or deactivate the plan","type":"boolean"},"planName":{"type":"string"},"planType":{"enum":["subscriber","hotspot"],"type":"string"},"priceAmount":{"description":"Cost per billing cycle","type":"number"},"priceCurrency":{"description":"ISO 4217 currency code","type":"string"},"uploadSpeedMbps":{"description":"Upload speed in Mbps","type":"number"}},"type":"object"},"ApiResponseDto":{"properties":{},"type":"object"},"PlanResponseDto":{"properties":{"billingCycleDays":{"description":"Length of one billing period in days (1, 7, 30, 90, or 365)","type":"number"},"createdAt":{"format":"date-time","type":"string"},"dataCapMb":{"description":"Monthly data allowance in MB (null = unlimited)","nullable":true,"type":"number"},"disabledAt":{"description":"Timestamp when the plan was disabled (null = active). Disabled plans are retained but cannot be assigned to new subscriptions; cohort 1 subs continue until the next renewal cycle.","format":"date-time","nullable":true,"type":"string"},"downloadSpeedMbps":{"description":"Download speed in Mbps","type":"number"},"effectiveDate":{"description":"When the package takes effect","format":"date-time","nullable":true,"type":"string"},"externalPackageId":{"description":"Package ID in the partner's platform","nullable":true,"type":"string"},"gracePeriodDays":{"description":"Grace period in days after overdue before suspension (0-30)","nullable":true,"type":"number"},"id":{"description":"Share internal identifier","format":"uuid","type":"string"},"installationFee":{"description":"One-time installation fee","type":"number"},"isActive":{"type":"boolean"},"planName":{"type":"string"},"planType":{"enum":["subscriber","hotspot"],"type":"string"},"priceAmount":{"description":"Cost per billing cycle","type":"number"},"priceCurrency":{"description":"ISO 4217 currency code","type":"string"},"radiusGroupName":{"description":"FreeRADIUS group name (generated by the backend, read-only)","nullable":true,"type":"string"},"speed":{"description":"Legacy speed field (mirrors download Mbps for backwards compatibility)","type":"number"},"updatedAt":{"format":"date-time","type":"string"},"uploadSpeedMbps":{"description":"Upload speed in Mbps","type":"number"}},"required":["id","externalPackageId","planType","planName","downloadSpeedMbps","uploadSpeedMbps","speed","priceAmount","priceCurrency","billingCycleDays","installationFee","isActive","createdAt","updatedAt"],"type":"object"}}},"paths":{"/v1/plans/{id}":{"put":{"description":"Updates the writable fields of an existing plan. The same up-front validation as create runs against the resolved fields (sent ∪ existing).\n\n**Error codes:** `PLAN_TYPE_NOT_SUPPORTED_VIA_API` (400), `PLAN_CURRENCY_NOT_SUPPORTED` (400), `PLAN_SPEED_EXCEEDS_1GBPS` (400), `PLAN_PRICE_BELOW_FLOOR` (400). See the [Error codes](../errors/error-codes.md) reference.","operationId":"PlansController_updatePlan_v1","parameters":[{"description":"Plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePlanRequestDto"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/PlanResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"The updated plan."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Update a plan","tags":["Plans"]}}}}
```

## Delete a plan

> Terminal soft-delete. The plan must already be disabled (via \`POST /v1/plans/:id/disable\`); otherwise the call returns \`409 PLAN\_NOT\_DISABLED\`. Once deleted, the plan disappears from \`GET /v1/plans\` (list and single-get) and can no longer be edited, duplicated, re-enabled, or matched by inbound Splynx tariff events. Callers must echo the plan name in the \`confirm\` query parameter (case-insensitive, whitespace-trimmed) to prevent accidental deletes.\
> \
> \*\*Error codes:\*\* \`CONFIRMATION\_MISMATCH\` (400) — \`confirm\` does not match the plan name; \`PLAN\_NOT\_DISABLED\` (409) — the plan is still active. See the \[Error codes]\(../errors/error-codes.md) reference.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"ApiResponseDto":{"properties":{},"type":"object"},"DeletePlanResponseDto":{"properties":{"deletedAt":{"description":"When the deletion took effect.","format":"date-time","type":"string"},"planId":{"format":"uuid","type":"string"}},"required":["planId","deletedAt"],"type":"object"}}},"paths":{"/v1/plans/{id}":{"delete":{"description":"Terminal soft-delete. The plan must already be disabled (via `POST /v1/plans/:id/disable`); otherwise the call returns `409 PLAN_NOT_DISABLED`. Once deleted, the plan disappears from `GET /v1/plans` (list and single-get) and can no longer be edited, duplicated, re-enabled, or matched by inbound Splynx tariff events. Callers must echo the plan name in the `confirm` query parameter (case-insensitive, whitespace-trimmed) to prevent accidental deletes.\n\n**Error codes:** `CONFIRMATION_MISMATCH` (400) — `confirm` does not match the plan name; `PLAN_NOT_DISABLED` (409) — the plan is still active. See the [Error codes](../errors/error-codes.md) reference.","operationId":"PlansController_deletePlan_v1","parameters":[{"description":"Plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Safety acknowledgement. Must echo the plan name (case-insensitive, whitespace-trimmed). A mismatch returns 400 with `errorCode = CONFIRMATION_MISMATCH`.","in":"query","name":"confirm","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/DeletePlanResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"Plan deleted."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Delete a plan","tags":["Plans"]}}}}
```

## Disable a plan

> Disables a plan while preserving the underlying record (a \`disabledAt\` timestamp is set). A disabled plan remains visible in \`GET /v1/plans\` and stays editable; it can later be re-enabled or deleted. Existing subscriptions are handled by cohort: paid, in-cycle subscriptions continue until renewal and are then transitioned to INACTIVE; PENDING and no-cycle subscriptions are deactivated immediately and RADIUS access is suspended; subscriptions queued onto this plan have their queued change cleared. Callers must echo the plan name in the \`confirm\` query parameter (case-insensitive, whitespace-trimmed) to prevent accidental disables. Use \`GET /v1/plans/:id/subscriber-count\` or \`GET /v1/plans/:id/disable-preview\` beforehand to gauge the subscriber impact; a disabled plan can be re-enabled via \`POST /v1/plans/:id/enable\`.\
> \
> \*\*Error codes:\*\* \`CONFIRMATION\_MISMATCH\` (400) — \`confirm\` query parameter does not match the plan name; \`PLAN\_ALREADY\_DISABLED\` (409) — the plan is already disabled. See the \[Error codes]\(../errors/error-codes.md) reference.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"ApiResponseDto":{"properties":{},"type":"object"},"DisablePlanResponseDto":{"properties":{"cohortCounts":{"$ref":"#/components/schemas/DisablePlanCohortCountsDto"},"disabledAt":{"description":"When the disable took effect.","format":"date-time","type":"string"},"planId":{"format":"uuid","type":"string"}},"required":["planId","disabledAt","cohortCounts"],"type":"object"},"DisablePlanCohortCountsDto":{"properties":{"paidActive":{"description":"Subscriptions on this plan with a paid + non-terminal cycle. Untouched on disable; the renewal cron transitions them to INACTIVE/PLAN_DISABLED at cycle end.","type":"number"},"pendingOrNoCycle":{"description":"PENDING subscriptions or subscriptions with no non-terminal cycle. Flipped to INACTIVE/PLAN_DISABLED immediately + RADIUS suspension enqueued.","type":"number"},"queuedAway":{"description":"Subscriptions on this plan with a queued `nextPlanId` pointing at a different plan. Untouched.","type":"number"},"queuedToThisPlan":{"description":"Subscriptions with `nextPlanId` queued onto this plan. Queue is cleared; current plan unchanged.","type":"number"},"unpaidOrSuspended":{"description":"Subscriptions on this plan in OVERDUE/SUSPENDED state. Existing grace flow handles them; the renewal cron skips when `disabledAt IS NOT NULL`.","type":"number"}},"required":["paidActive","pendingOrNoCycle","queuedToThisPlan","queuedAway","unpaidOrSuspended"],"type":"object"}}},"paths":{"/v1/plans/{id}/disable":{"post":{"description":"Disables a plan while preserving the underlying record (a `disabledAt` timestamp is set). A disabled plan remains visible in `GET /v1/plans` and stays editable; it can later be re-enabled or deleted. Existing subscriptions are handled by cohort: paid, in-cycle subscriptions continue until renewal and are then transitioned to INACTIVE; PENDING and no-cycle subscriptions are deactivated immediately and RADIUS access is suspended; subscriptions queued onto this plan have their queued change cleared. Callers must echo the plan name in the `confirm` query parameter (case-insensitive, whitespace-trimmed) to prevent accidental disables. Use `GET /v1/plans/:id/subscriber-count` or `GET /v1/plans/:id/disable-preview` beforehand to gauge the subscriber impact; a disabled plan can be re-enabled via `POST /v1/plans/:id/enable`.\n\n**Error codes:** `CONFIRMATION_MISMATCH` (400) — `confirm` query parameter does not match the plan name; `PLAN_ALREADY_DISABLED` (409) — the plan is already disabled. See the [Error codes](../errors/error-codes.md) reference.","operationId":"PlansController_disablePlan_v1","parameters":[{"description":"Plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Safety acknowledgement. Must echo the plan name (case-insensitive, whitespace-trimmed). A mismatch returns 400 with `errorCode = CONFIRMATION_MISMATCH`.","in":"query","name":"confirm","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/DisablePlanResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"Plan disabled. The response includes a cohort breakdown so the caller can audit downstream effects."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Disable a plan","tags":["Plans"]}}}}
```

## Preview the effect of disabling a plan

> Side-effect-free dry-run for the disable flow: returns the same five-cohort breakdown that \`POST /v1/plans/:id/disable\` would report, without changing any state. Cohort discrimination matches the disable handler exactly, so the predicted counts line up with the actual disable response.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"ApiResponseDto":{"properties":{},"type":"object"},"DisablePreviewResponseDto":{"properties":{"cohortCounts":{"$ref":"#/components/schemas/DisablePlanCohortCountsDto"}},"required":["cohortCounts"],"type":"object"},"DisablePlanCohortCountsDto":{"properties":{"paidActive":{"description":"Subscriptions on this plan with a paid + non-terminal cycle. Untouched on disable; the renewal cron transitions them to INACTIVE/PLAN_DISABLED at cycle end.","type":"number"},"pendingOrNoCycle":{"description":"PENDING subscriptions or subscriptions with no non-terminal cycle. Flipped to INACTIVE/PLAN_DISABLED immediately + RADIUS suspension enqueued.","type":"number"},"queuedAway":{"description":"Subscriptions on this plan with a queued `nextPlanId` pointing at a different plan. Untouched.","type":"number"},"queuedToThisPlan":{"description":"Subscriptions with `nextPlanId` queued onto this plan. Queue is cleared; current plan unchanged.","type":"number"},"unpaidOrSuspended":{"description":"Subscriptions on this plan in OVERDUE/SUSPENDED state. Existing grace flow handles them; the renewal cron skips when `disabledAt IS NOT NULL`.","type":"number"}},"required":["paidActive","pendingOrNoCycle","queuedToThisPlan","queuedAway","unpaidOrSuspended"],"type":"object"}}},"paths":{"/v1/plans/{id}/disable-preview":{"get":{"description":"Side-effect-free dry-run for the disable flow: returns the same five-cohort breakdown that `POST /v1/plans/:id/disable` would report, without changing any state. Cohort discrimination matches the disable handler exactly, so the predicted counts line up with the actual disable response.","operationId":"PlansController_getDisablePreview_v1","parameters":[{"description":"Plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/DisablePreviewResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"Cohort breakdown predicted for the disable."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Preview the effect of disabling a plan","tags":["Plans"]}}}}
```

## Re-enable a disabled plan

> Clears \`disabledAt\` so the plan can be assigned to subscriptions again — the counterpart to \`POST /v1/plans/:id/disable\`. Paid, in-cycle subscribers naturally renew at the next billing tick once the plan is active again; subscribers that were deactivated by the disable are deliberately not restored. No confirmation parameter is required — re-enabling is non-destructive.\
> \
> \*\*Error codes:\*\* \`PLAN\_ALREADY\_ENABLED\` (409) — the plan is not disabled. See the \[Error codes]\(../errors/error-codes.md) reference.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"ApiResponseDto":{"properties":{},"type":"object"},"EnablePlanResponseDto":{"properties":{"disabledAt":{"description":"Always null on a successful re-enable — surfaced for symmetry with the disable response shape (callers can branch on a single field).","format":"date-time","nullable":true,"type":"string"},"enabledAt":{"description":"When the re-enable took effect.","format":"date-time","type":"string"},"planId":{"format":"uuid","type":"string"}},"required":["planId","disabledAt","enabledAt"],"type":"object"}}},"paths":{"/v1/plans/{id}/enable":{"post":{"description":"Clears `disabledAt` so the plan can be assigned to subscriptions again — the counterpart to `POST /v1/plans/:id/disable`. Paid, in-cycle subscribers naturally renew at the next billing tick once the plan is active again; subscribers that were deactivated by the disable are deliberately not restored. No confirmation parameter is required — re-enabling is non-destructive.\n\n**Error codes:** `PLAN_ALREADY_ENABLED` (409) — the plan is not disabled. See the [Error codes](../errors/error-codes.md) reference.","operationId":"PlansController_enablePlan_v1","parameters":[{"description":"Plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/EnablePlanResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"Plan re-enabled."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Re-enable a disabled plan","tags":["Plans"]}}}}
```

## Count subscribers on a plan

> Lightweight two-bucket aggregate: \`activeOnPlan\` (subscriptions currently on this plan, status ACTIVE or PENDING) + \`queuedForPlan\` (subscriptions queued onto it via a pending plan change). Use it as a pre-disable impact check; \`GET /v1/plans/:id/disable-preview\` returns the full five-cohort forecast instead.

```json
{"openapi":"3.0.0","info":{"title":"Share API","version":"1.0"},"tags":[{"description":"CRUD on Share plans (the catalog partners offer to subscribers). Plan type, currency, speed, and price are validated up-front against Schedule D — only `RESIDENTIAL` plans are creatable via the public API, speed must be ≤ 1 Gbps, and price must clear the currency/type-specific floor. Lifecycle is two-step: `POST /v1/plans/:id/disable` runs the cohort-based disable (the plan stays listed and editable, and can be re-enabled), then `DELETE /v1/plans/:id` performs the terminal soft-delete (only permitted once disabled; the plan then drops out of list/get). See the operation descriptions and Subscription states guide.","name":"Plans"}],"security":[{"X-Access-Secret":[]},{"X-Access-Id":[]}],"components":{"securitySchemes":{"X-Access-Secret":{"description":"Provider Access Secret Key (JWT)","in":"header","name":"X-Access-Secret","type":"apiKey"},"X-Access-Id":{"description":"Provider Access ID","in":"header","name":"X-Access-Id","type":"apiKey"}},"schemas":{"ApiResponseDto":{"properties":{},"type":"object"},"SubscriberCountResponseDto":{"properties":{"activeOnPlan":{"description":"Subscriptions where `planId == :id` and status is ACTIVE or PENDING.","type":"number"},"count":{"description":"`activeOnPlan + queuedForPlan`. Convenience aggregate.","type":"number"},"queuedForPlan":{"description":"Subscriptions queued onto this plan via `nextPlanId`. INACTIVE subs are excluded so a stale queue on a dead row does not inflate the count.","type":"number"}},"required":["count","activeOnPlan","queuedForPlan"],"type":"object"}}},"paths":{"/v1/plans/{id}/subscriber-count":{"get":{"description":"Lightweight two-bucket aggregate: `activeOnPlan` (subscriptions currently on this plan, status ACTIVE or PENDING) + `queuedForPlan` (subscriptions queued onto it via a pending plan change). Use it as a pre-disable impact check; `GET /v1/plans/:id/disable-preview` returns the full five-cohort forecast instead.","operationId":"PlansController_getSubscriberCount_v1","parameters":[{"description":"Plan identifier (UUID).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiResponseDto"},{"properties":{"data":{"$ref":"#/components/schemas/SubscriberCountResponseDto"},"success":{"type":"boolean"}}}]}}},"description":"Subscriber count for the plan."},"400":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"rejectionReasons":{"items":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"type":"array"},"success":{"type":"boolean"}}}}},"description":"Validation failed"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Unauthorized — missing or invalid credentials"},"403":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Forbidden — insufficient permissions"},"404":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Resource not found"},"408":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Request timeout"},"500":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"success":{"type":"boolean"}}}}},"description":"Internal server error"}},"summary":"Count subscribers on a plan","tags":["Plans"]}}}}
```
