Operation details
GET /v1/admin/packages
List and filter package products
- operationId
listPackages- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
The contract has no separate permission annotation; see the description below and the 403 response.
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; each scope sees and changes only the groups and packages it owns (owner_scope_id), and another owner's package returns permission_denied. Unknown or repeated query parameters are rejected.
Parameters
listPackages Parameters| Name | In | Type | Required | Description |
|---|
page | query | integer | No | — |
|---|
limit | query | integer | No | — |
|---|
search | query | string | No | Case-insensitive substring match against the product name. |
|---|
group_id | query | string(uuid) | No | Return products whose group_ids contain this exact group UUID. |
|---|
enabled | query | boolean | No | — |
|---|
published | query | boolean | No | true keeps only products whose current revision is published (purchasable); false only the others. |
|---|
Responses
listPackages Responses| Status | Description |
|---|
200 | Stable product page ordered by sort order and then name. Fields:items: array<PackageProduct> total: integer(int64) page: integer limit: integer |
|---|
400 | The request body or parameters are invalid. |
|---|
401 | Authentication failed or the session is invalid. |
|---|
403 | The authenticated principal is not allowed to perform the action. |
|---|
500 | The request could not be completed because of an internal failure. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.500 Outcome unknown: read back by ID or list first, then decide whether to retry with the original Idempotency-Key.
↑ Back to list
POST /v1/admin/packages
Create a versioned package product
- operationId
createPackage- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
The contract has no separate permission annotation; see the description below and the 403 response.
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; each scope sees and changes only the groups and packages it owns (owner_scope_id), and another owner's package returns permission_denied. Referenced package groups must exist and be active. Region and node-group UUIDs are currently opaque configuration references and are not validated against region or node-group resources. A reseller's main and backup line groups must be platform line groups granted to it (scope_may_use_line_group_460); an ungranted one returns 400 package_line_group_not_granted. A reseller price below the wholesale price of its line groups in its price list returns 400 package_price_below_wholesale with details period, price_minor, wholesale_minor and price_list.
Parameters
createPackage Parameters| Name | In | Type | Required | Description |
|---|
Idempotency-Key | header | string | Yes | — |
|---|
Request body
application/json · Required:Yes
createPackage Request body| Name | Type | Required | Description |
|---|
name | string | Yes | — |
|---|
description | string | Yes | — |
|---|
group_ids | array<string(uuid)> | Yes | Active package-group UUIDs. These references are validated by the catalog service. |
|---|
region_id | string(uuid) | Yes | Opaque non-zero region UUID. The current catalog does not validate a backing region resource. |
|---|
node_group_id | string(uuid) | Yes | Opaque non-zero primary node-group UUID. The current catalog does not validate a backing node-group resource. |
|---|
backup_node_group_id | string(uuid) | null(uuid) | Yes | Optional opaque backup node-group UUID; when set it must differ from node_group_id. No backing resource existence check is implemented yet. |
|---|
limits | PackageResourceLimits | Yes | — |
|---|
protection | PackageProtectionDescription | Yes | — |
|---|
prices | PackagePrices | Yes | — |
|---|
cname | PackageCNAMEPolicy | Yes | — |
|---|
purchase | PackagePurchasePolicy | Yes | — |
|---|
sort_order | integer | Yes | — |
|---|
enabled | boolean | Yes | Set false to disable the package in the product catalog. |
|---|
Responses
createPackage Responses| Status | Description |
|---|
201 | Product and immutable revision-one snapshot created, or an identical idempotent request replayed. Fields:id: string(uuid) owner_scope_id: string(uuid) spec: PackageProductSpec revision: integer(int64) created_at: string(date-time) updated_at: string(date-time) cname_sync_job_id?: string(uuid) |
|---|
400 | The request body or parameters are invalid. |
|---|
401 | Authentication failed or the session is invalid. |
|---|
403 | The authenticated principal is not allowed to perform the action. |
|---|
409 | The requested state conflicts with an existing resource or idempotency fact. |
|---|
500 | The request could not be completed because of an internal failure. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.409 Read the current state first; a retry of the same operation keeps the original Idempotency-Key and body.500 Outcome unknown: read back by ID or list first, then decide whether to retry with the original Idempotency-Key.
↑ Back to list
GET /v1/admin/packages/{id}
Get one active package product
- operationId
getPackage- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
The contract has no separate permission annotation; see the description below and the 403 response.
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; each scope sees and changes only the groups and packages it owns (owner_scope_id), and another owner's package returns permission_denied.
Parameters
getPackage Parameters| Name | In | Type | Required | Description |
|---|
id | path | string(uuid) | Yes | Package group or package product UUID. |
|---|
Responses
getPackage Responses| Status | Description |
|---|
200 | Current product specification and revision. Fields:id: string(uuid) owner_scope_id: string(uuid) spec: PackageProductSpec revision: integer(int64) created_at: string(date-time) updated_at: string(date-time) cname_sync_job_id?: string(uuid) |
|---|
400 | The request body or parameters are invalid. |
|---|
401 | Authentication failed or the session is invalid. |
|---|
403 | The authenticated principal is not allowed to perform the action. |
|---|
404 | The requested resource was not found. |
|---|
500 | The request could not be completed because of an internal failure. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.500 Outcome unknown: read back by ID or list first, then decide whether to retry with the original Idempotency-Key.
↑ Back to list
PATCH /v1/admin/packages/{id}
Replace a package specification, including enable or disable state
- operationId
updatePackage- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
The contract has no separate permission annotation; see the description below and the 403 response.
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; each scope sees and changes only the groups and packages it owns (owner_scope_id), and another owner's package returns permission_denied. expected_revision is checked atomically, and every successful change creates a new immutable revision snapshot. Region and node-group UUIDs remain opaque configuration references without resource existence validation. A reseller's main and backup line groups must be platform line groups granted to it (scope_may_use_line_group_460); an ungranted one returns 400 package_line_group_not_granted. A reseller price below the wholesale price of its line groups in its price list returns 400 package_price_below_wholesale with details period, price_minor, wholesale_minor and price_list.
Parameters
updatePackage Parameters| Name | In | Type | Required | Description |
|---|
id | path | string(uuid) | Yes | Package group or package product UUID. |
|---|
Idempotency-Key | header | string | Yes | — |
|---|
Request body
application/json · Required:Yes
updatePackage Request body| Name | Type | Required | Description |
|---|
expected_revision | integer(int64) | Yes | — |
|---|
spec | PackageProductSpec | Yes | — |
|---|
Responses
updatePackage Responses| Status | Description |
|---|
200 | Product specification replaced and revision incremented, or an identical idempotent request replayed. Fields:id: string(uuid) owner_scope_id: string(uuid) spec: PackageProductSpec revision: integer(int64) created_at: string(date-time) updated_at: string(date-time) cname_sync_job_id?: string(uuid) |
|---|
400 | The request body or parameters are invalid. |
|---|
401 | Authentication failed or the session is invalid. |
|---|
403 | The authenticated principal is not allowed to perform the action. |
|---|
404 | The requested resource was not found. |
|---|
409 | The requested state conflicts with an existing resource or idempotency fact. |
|---|
500 | The request could not be completed because of an internal failure. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.409 Read the current state first; a retry of the same operation keeps the original Idempotency-Key and body.500 Outcome unknown: read back by ID or list first, then decide whether to retry with the original Idempotency-Key.
↑ Back to list
DELETE /v1/admin/packages/{id}
Soft-delete a package product
- operationId
deletePackage- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
The contract has no separate permission annotation; see the description below and the 403 response.
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; each scope sees and changes only the groups and packages it owns (owner_scope_id), and another owner's package returns permission_denied. If-Match supplies the expected revision, and the deletion is recorded as the next immutable product snapshot.
Parameters
deletePackage Parameters| Name | In | Type | Required | Description |
|---|
id | path | string(uuid) | Yes | Package group or package product UUID. |
|---|
Idempotency-Key | header | string | Yes | — |
|---|
If-Match | header | string | Yes | — |
|---|
Responses
deletePackage Responses| Status | Description |
|---|
204 | Product soft-deleted, or an identical idempotent request replayed. |
|---|
400 | The request body or parameters are invalid. |
|---|
401 | Authentication failed or the session is invalid. |
|---|
403 | The authenticated principal is not allowed to perform the action. |
|---|
404 | The requested resource was not found. |
|---|
409 | The requested state conflicts with an existing resource or idempotency fact. |
|---|
500 | The request could not be completed because of an internal failure. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.409 Read the current state first; a retry of the same operation keeps the original Idempotency-Key and body.500 Outcome unknown: read back by ID or list first, then decide whether to retry with the original Idempotency-Key.
↑ Back to list
GET /v1/admin/packages/{id}/cname-sync/{job_id}
Progress of the sync of a package CNAME change to its sold subscriptions
- operationId
getPackageCNAMESync- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
The contract has no separate permission annotation; see the description below and the 403 response.
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; each scope sees and changes only the groups and packages it owns (owner_scope_id), and another owner's package returns permission_denied. The job is created by the package update that changed the CNAME (cname_sync_job_id). skipped lists at most 200 subscriptions; skipped_count is the total.
Parameters
getPackageCNAMESync Parameters| Name | In | Type | Required | Description |
|---|
id | path | string(uuid) | Yes | Package group or package product UUID. |
|---|
job_id | path | string(uuid) | Yes | Package CNAME sync job UUID (cname_sync_job_id of the package update response). |
|---|
Responses
getPackageCNAMESync Responses| Status | Description |
|---|
200 | Sync job counters and skipped subscriptions. Fields:id: string(uuid) product_id: string(uuid) product_revision: integer(int64) state: string: queued / running / succeeded / failed synced_count: integer(int64) site_count: integer(int64) skipped_count: integer(int64) attempt: integer max_attempts: integer last_error_code: string | null skipped: array<object> created_at: string(date-time) updated_at: string(date-time) |
|---|
400 | The request body or parameters are invalid. |
|---|
401 | Authentication failed or the session is invalid. |
|---|
403 | The authenticated principal is not allowed to perform the action. |
|---|
404 | The requested resource was not found. |
|---|
500 | The request could not be completed because of an internal failure. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.500 Outcome unknown: read back by ID or list first, then decide whether to retry with the original Idempotency-Key.
↑ Back to list
POST /v1/admin/packages/{id}/cname-sync/{job_id}/retry
Retry a failed package CNAME sync with the caller's session
- operationId
retryPackageCNAMESync- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
The contract has no separate permission annotation; see the description below and the 403 response.
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; each scope sees and changes only the groups and packages it owns (owner_scope_id), and another owner's package returns permission_denied. Only a failed job (for example actor_session_ended) can be retried; already processed subscriptions are not processed again.
Parameters
retryPackageCNAMESync Parameters| Name | In | Type | Required | Description |
|---|
id | path | string(uuid) | Yes | Package group or package product UUID. |
|---|
job_id | path | string(uuid) | Yes | Package CNAME sync job UUID (cname_sync_job_id of the package update response). |
|---|
Idempotency-Key | header | string | Yes | — |
|---|
Responses
retryPackageCNAMESync Responses| Status | Description |
|---|
200 | Job requeued. Fields:id: string(uuid) product_id: string(uuid) product_revision: integer(int64) state: string: queued / running / succeeded / failed synced_count: integer(int64) site_count: integer(int64) skipped_count: integer(int64) attempt: integer max_attempts: integer last_error_code: string | null skipped: array<object> created_at: string(date-time) updated_at: string(date-time) |
|---|
400 | The request body or parameters are invalid. |
|---|
401 | Authentication failed or the session is invalid. |
|---|
403 | The authenticated principal is not allowed to perform the action. |
|---|
404 | The requested resource was not found. |
|---|
409 | The requested state conflicts with an existing resource or idempotency fact. |
|---|
500 | The request could not be completed because of an internal failure. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.409 Read the current state first; a retry of the same operation keeps the original Idempotency-Key and body.500 Outcome unknown: read back by ID or list first, then decide whether to retry with the original Idempotency-Key.
↑ Back to list
POST /v1/admin/packages/{id}/publish
Publish the current immutable package product revision
- operationId
publishPackageOffer- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
All of:packages.manage
Description (from the contract)
The platform needs packages.manage; an active reseller tenant may publish only its own package with reseller.packages.manage. A reseller package whose line group grant was revoked, or whose price fell below the wholesale price, cannot be published again (400 package_line_group_not_granted / package_price_below_wholesale). packages.count counts the owner's published packages.
Parameters
publishPackageOffer Parameters| Name | In | Type | Required | Description |
|---|
id | path | string(uuid) | Yes | — |
|---|
If-Match | header | string | Yes | Quoted positive immutable product revision. |
|---|
Idempotency-Key | header | string | Yes | Exact retries reuse this key with the same action, product, and revision. |
|---|
Responses
publishPackageOffer Responses| Status | Description |
|---|
200 | Current durable publication head or identical committed replay. Fields:product_id: string(uuid) product_revision: integer(int64) lifecycle_revision: integer(int64) state: string: published / withdrawn published_at: string(date-time) withdrawn_at?: string(date-time) |
|---|
400 | Stable package publication error. |
|---|
401 | Stable package publication error. |
|---|
403 | Stable package publication error. |
|---|
404 | Stable package publication error. |
|---|
409 | Stable package publication error. |
|---|
412 | Stable package publication error. |
|---|
422 | Stable package publication error. |
|---|
503 | Stable package publication error. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.409 Read the current state first; a retry of the same operation keeps the original Idempotency-Key and body.412 The revision changed: read the latest ETag, decide again, then send the new If-Match.503 A dependency is unavailable: retry later with the original Idempotency-Key.
↑ Back to list
POST /v1/admin/packages/{id}/withdraw
Withdraw the exact immutable package revision named by If-Match
- operationId
withdrawPackageOffer- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
All of:packages.manage
Description (from the contract)
If-Match binds the previously published product revision, not a newer mutable draft revision. Existing sold subscriptions remain unchanged. An active reseller tenant may withdraw only its own package (reseller.packages.manage); revoking a line group grant withdraws the reseller packages that use it.
Parameters
withdrawPackageOffer Parameters| Name | In | Type | Required | Description |
|---|
id | path | string(uuid) | Yes | — |
|---|
If-Match | header | string | Yes | Quoted positive immutable product revision. |
|---|
Idempotency-Key | header | string | Yes | Exact retries reuse this key with the same action, product, and revision. |
|---|
Responses
withdrawPackageOffer Responses| Status | Description |
|---|
200 | Current durable publication head or identical committed replay. Fields:product_id: string(uuid) product_revision: integer(int64) lifecycle_revision: integer(int64) state: string: published / withdrawn published_at: string(date-time) withdrawn_at?: string(date-time) |
|---|
400 | Stable package publication error. |
|---|
401 | Stable package publication error. |
|---|
403 | Stable package publication error. |
|---|
404 | Stable package publication error. |
|---|
409 | Stable package publication error. |
|---|
412 | Stable package publication error. |
|---|
422 | Stable package publication error. |
|---|
503 | Stable package publication error. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.409 Read the current state first; a retry of the same operation keeps the original Idempotency-Key and body.412 The revision changed: read the latest ETag, decide again, then send the new If-Match.503 A dependency is unavailable: retry later with the original Idempotency-Key.
↑ Back to list
GET /v1/admin/packages/{product_id}/escalate-policy
Read a package's attack escalation policy
- operationId
getPackageEscalatePolicy- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
All of:packages.manage reseller.packages.manage
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; only the package owner (owner_scope_id) reads it, another owner's package returns 403 escalate_permission_denied.
Parameters
getPackageEscalatePolicy Parameters| Name | In | Type | Required | Description |
|---|
product_id | path | string(uuid) | Yes | — |
|---|
Responses
getPackageEscalatePolicy Responses| Status | Description |
|---|
200 | The package policy. Fields:product_id?: string(uuid) b: LineGroupRef | null c: LineGroupRef | null c_cloudflare: boolean blackhole_enabled: boolean thresholds: object sustain_seconds: integer recover_permille: integer min_dwell_seconds: integer revision: integer(int64) b_line_group_name?: string c_line_group_name?: string |
|---|
400 | Error. |
|---|
401 | Error. |
|---|
403 | Error. |
|---|
404 | Error. |
|---|
503 | Error. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.503 A dependency is unavailable: retry later with the original Idempotency-Key.
↑ Back to list
PUT /v1/admin/packages/{product_id}/escalate-policy
Write a package's B/C line groups, Cloudflare and blackhole switches and threshold ranges
- operationId
putPackageEscalatePolicy- Auth
- Session: the Bearer session of a signed-in control plane user, used by the customer in their own control plane; a plugin must never ask for it.
- Required permissions
All of:packages.manage reseller.packages.manage
Required features:module.escalate
Description (from the contract)
Requires packages.manage in the platform scope, or reseller.packages.manage in an active reseller tenant scope; only the package owner (owner_scope_id) writes it, another owner's package returns 403 escalate_permission_denied. A is the package's main line group. B must be a high defense line group and C an unlimited one the package owner may use (its own or granted to the reseller, 422 escalate_line_group_unusable otherwise). revision is the current revision (0 creates).
Parameters
putPackageEscalatePolicy Parameters| Name | In | Type | Required | Description |
|---|
product_id | path | string(uuid) | Yes | — |
|---|
Request body
application/json · Required:Yes
putPackageEscalatePolicy Request body| Name | Type | Required | Description |
|---|
product_id | string(uuid) | No | — |
|---|
b | LineGroupRef | null | Yes | — |
|---|
c | LineGroupRef | null | Yes | — |
|---|
c_cloudflare | boolean | Yes | C may use the site owner's Cloudflare when bound and synced. |
|---|
blackhole_enabled | boolean | Yes | — |
|---|
thresholds | object | Yes | — |
|---|
sustain_seconds | integer | Yes | — |
|---|
recover_permille | integer | Yes | — |
|---|
min_dwell_seconds | integer | Yes | — |
|---|
revision | integer(int64) | Yes | — |
|---|
b_line_group_name | string | No | Name of B's line group, filled on every read; absent when B is null. A write ignores it. |
|---|
c_line_group_name | string | No | Name of C's line group, filled on every read; absent when C is null. A write ignores it. |
|---|
Responses
putPackageEscalatePolicy Responses| Status | Description |
|---|
200 | The package policy. Fields:product_id?: string(uuid) b: LineGroupRef | null c: LineGroupRef | null c_cloudflare: boolean blackhole_enabled: boolean thresholds: object sustain_seconds: integer recover_permille: integer min_dwell_seconds: integer revision: integer(int64) b_line_group_name?: string c_line_group_name?: string |
|---|
400 | Error. |
|---|
401 | Error. |
|---|
403 | Error. |
|---|
404 | Error. |
|---|
409 | Error. |
|---|
422 | Error. |
|---|
503 | Error. |
|---|
What to do on failure
400 Fix the parameters or body per the field list; do not retry unchanged.401 Check the credential format and whether it expired or was rotated.403 Check the key scopes, the owner’s current permissions and the tenant scope; never switch to a more privileged account.404 The resource does not exist or belongs to another tenant.409 Read the current state first; a retry of the same operation keeps the original Idempotency-Key and body.503 A dependency is unavailable: retry later with the original Idempotency-Key.
↑ Back to list