MethodPathSummaryAuth
GET/v1/admin/packagesList and filter package productsSession
POST/v1/admin/packagesCreate a versioned package productSession
GET/v1/admin/packages/{id}Get one active package productSession
PATCH/v1/admin/packages/{id}Replace a package specification, including enable or disable stateSession
DELETE/v1/admin/packages/{id}Soft-delete a package productSession
GET/v1/admin/packages/{id}/cname-sync/{job_id}Progress of the sync of a package CNAME change to its sold subscriptionsSession
POST/v1/admin/packages/{id}/cname-sync/{job_id}/retryRetry a failed package CNAME sync with the caller's sessionSession
POST/v1/admin/packages/{id}/publishPublish the current immutable package product revisionSession
POST/v1/admin/packages/{id}/withdrawWithdraw the exact immutable package revision named by If-MatchSession
GET/v1/admin/packages/{product_id}/escalate-policyRead a package's attack escalation policySession
PUT/v1/admin/packages/{product_id}/escalate-policyWrite a package's B/C line groups, Cloudflare and blackhole switches and threshold rangesSession

Contract file:api/openapi/v1/control.yaml api/control-escalate.openapi.yaml

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
NameInTypeRequiredDescription
pagequeryintegerNo—
limitqueryintegerNo—
searchquerystringNoCase-insensitive substring match against the product name.
group_idquerystring(uuid)NoReturn products whose group_ids contain this exact group UUID.
enabledquerybooleanNo—
publishedquerybooleanNotrue keeps only products whose current revision is published (purchasable); false only the others.

Responses

listPackages Responses
StatusDescription
200Stable product page ordered by sort order and then name.

Fields:items: array<PackageProduct> total: integer(int64) page: integer limit: integer

400The request body or parameters are invalid.
401Authentication failed or the session is invalid.
403The authenticated principal is not allowed to perform the action.
500The 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
NameInTypeRequiredDescription
Idempotency-KeyheaderstringYes—

Request body

application/json · Required:Yes

createPackage Request body
NameTypeRequiredDescription
namestringYes—
descriptionstringYes—
group_idsarray<string(uuid)>YesActive package-group UUIDs. These references are validated by the catalog service.
region_idstring(uuid)YesOpaque non-zero region UUID. The current catalog does not validate a backing region resource.
node_group_idstring(uuid)YesOpaque non-zero primary node-group UUID. The current catalog does not validate a backing node-group resource.
backup_node_group_idstring(uuid) | null(uuid)YesOptional opaque backup node-group UUID; when set it must differ from node_group_id. No backing resource existence check is implemented yet.
limitsPackageResourceLimitsYes—
protectionPackageProtectionDescriptionYes—
pricesPackagePricesYes—
cnamePackageCNAMEPolicyYes—
purchasePackagePurchasePolicyYes—
sort_orderintegerYes—
enabledbooleanYesSet false to disable the package in the product catalog.

Responses

createPackage Responses
StatusDescription
201Product 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)

400The request body or parameters are invalid.
401Authentication failed or the session is invalid.
403The authenticated principal is not allowed to perform the action.
409The requested state conflicts with an existing resource or idempotency fact.
500The 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
NameInTypeRequiredDescription
idpathstring(uuid)YesPackage group or package product UUID.

Responses

getPackage Responses
StatusDescription
200Current 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)

400The request body or parameters are invalid.
401Authentication failed or the session is invalid.
403The authenticated principal is not allowed to perform the action.
404The requested resource was not found.
500The 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
NameInTypeRequiredDescription
idpathstring(uuid)YesPackage group or package product UUID.
Idempotency-KeyheaderstringYes—

Request body

application/json · Required:Yes

updatePackage Request body
NameTypeRequiredDescription
expected_revisioninteger(int64)Yes—
specPackageProductSpecYes—

Responses

updatePackage Responses
StatusDescription
200Product 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)

400The request body or parameters are invalid.
401Authentication failed or the session is invalid.
403The authenticated principal is not allowed to perform the action.
404The requested resource was not found.
409The requested state conflicts with an existing resource or idempotency fact.
500The 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
NameInTypeRequiredDescription
idpathstring(uuid)YesPackage group or package product UUID.
Idempotency-KeyheaderstringYes—
If-MatchheaderstringYes—

Responses

deletePackage Responses
StatusDescription
204Product soft-deleted, or an identical idempotent request replayed.
400The request body or parameters are invalid.
401Authentication failed or the session is invalid.
403The authenticated principal is not allowed to perform the action.
404The requested resource was not found.
409The requested state conflicts with an existing resource or idempotency fact.
500The 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
NameInTypeRequiredDescription
idpathstring(uuid)YesPackage group or package product UUID.
job_idpathstring(uuid)YesPackage CNAME sync job UUID (cname_sync_job_id of the package update response).

Responses

getPackageCNAMESync Responses
StatusDescription
200Sync 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)

400The request body or parameters are invalid.
401Authentication failed or the session is invalid.
403The authenticated principal is not allowed to perform the action.
404The requested resource was not found.
500The 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
NameInTypeRequiredDescription
idpathstring(uuid)YesPackage group or package product UUID.
job_idpathstring(uuid)YesPackage CNAME sync job UUID (cname_sync_job_id of the package update response).
Idempotency-KeyheaderstringYes—

Responses

retryPackageCNAMESync Responses
StatusDescription
200Job 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)

400The request body or parameters are invalid.
401Authentication failed or the session is invalid.
403The authenticated principal is not allowed to perform the action.
404The requested resource was not found.
409The requested state conflicts with an existing resource or idempotency fact.
500The 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
NameInTypeRequiredDescription
idpathstring(uuid)Yes—
If-MatchheaderstringYesQuoted positive immutable product revision.
Idempotency-KeyheaderstringYesExact retries reuse this key with the same action, product, and revision.

Responses

publishPackageOffer Responses
StatusDescription
200Current 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)

400Stable package publication error.
401Stable package publication error.
403Stable package publication error.
404Stable package publication error.
409Stable package publication error.
412Stable package publication error.
422Stable package publication error.
503Stable 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
NameInTypeRequiredDescription
idpathstring(uuid)Yes—
If-MatchheaderstringYesQuoted positive immutable product revision.
Idempotency-KeyheaderstringYesExact retries reuse this key with the same action, product, and revision.

Responses

withdrawPackageOffer Responses
StatusDescription
200Current 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)

400Stable package publication error.
401Stable package publication error.
403Stable package publication error.
404Stable package publication error.
409Stable package publication error.
412Stable package publication error.
422Stable package publication error.
503Stable 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
NameInTypeRequiredDescription
product_idpathstring(uuid)Yes—

Responses

getPackageEscalatePolicy Responses
StatusDescription
200The 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

400Error.
401Error.
403Error.
404Error.
503Error.

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
NameInTypeRequiredDescription
product_idpathstring(uuid)Yes—

Request body

application/json · Required:Yes

putPackageEscalatePolicy Request body
NameTypeRequiredDescription
product_idstring(uuid)No—
bLineGroupRef | nullYes—
cLineGroupRef | nullYes—
c_cloudflarebooleanYesC may use the site owner's Cloudflare when bound and synced.
blackhole_enabledbooleanYes—
thresholdsobjectYes—
sustain_secondsintegerYes—
recover_permilleintegerYes—
min_dwell_secondsintegerYes—
revisioninteger(int64)Yes—
b_line_group_namestringNoName of B's line group, filled on every read; absent when B is null. A write ignores it.
c_line_group_namestringNoName of C's line group, filled on every read; absent when C is null. A write ignores it.

Responses

putPackageEscalatePolicy Responses
StatusDescription
200The 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

400Error.
401Error.
403Error.
404Error.
409Error.
422Error.
503Error.

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