Skip to content

Blog ·

How to sell web hosting with Plesk

Turning a Plesk server into a hosting product: one key for two APIs, the suspend trap that hides an account from your own polling, and what we never ask for.

By Provibr

Plesk will happily run a hundred subscriptions for you. What it will not do is take an order, collect the money, notice an invoice went unpaid, or suspend the account that belongs to it. That gap between "a panel that hosts websites" and "a hosting business" is where the work sits, and this page is about crossing it without stepping in the four places that are easy to step in.

What Plesk already gives you

A subscription is the unit you sell: a main domain, a service plan, disk and traffic limits, and everything under it. A service plan is the panel's own description of that package: quota, monthly traffic, how many domains and mailboxes, which services are on. Both concepts already exist, so you are not inventing a product model, you are naming one that is there.

The API is good in one way that matters especially for a hosting product: one call reads every subscription on the server, with its plan, status, quota and usage. We measured that at 0.33 seconds regardless of how many there are. Three read calls give you the full picture of a server (subscriptions, clients, plans), and that number does not grow with your customer count. Building the same picture over REST would take one call plus three per subscription.

One key opens two APIs, and you need both

Plesk has a modern REST v2 API and an older XML/RPC API. Neither is complete on its own, and that is not a matter of preference.

REST v2 has no subscriptions and no service plans. We enumerated its 43 paths from its own OpenAPI document; /subscriptions and /service-plans are not among them, and asking for them returns a 404 rendered as an HTML page rather than JSON. Since the service plan is the hosting package, the whole package side has to run over XML.

The saving grace is the credential. The key issued by POST /api/v2/auth/keys works unchanged in both: as X-API-Key on REST and as KEY on the XML endpoint. Measured, same key, both APIs. So you ask the operator for one value, not two.

What it costs you is two failure vocabularies. REST answers an unusable key with HTTP 401 and a completely empty body. XML answers the same key with HTTP 200 and an error code inside the document. Which brings us to the sharp edge.

An authentication failure that looks like an empty server

Because the XML API returns HTTP 200 for a bad key, a client that checks the status code reads an authentication failure as a successful round that found nothing. And a successful round that found nothing has a meaning in any hosting platform: every account that was there yesterday is gone.

The consequence is not a broken dashboard. It is a system that marks every one of your customers as deleted because a key expired. The rule that follows is absolute: on the XML API the status code carries no information at all, not even about authentication. Parse the document, and treat an unreadable answer as unknown rather than as empty.

An empty server does not produce an error either. It produces a single empty result, which is the correct answer when there really are none. Telling those two apart is the whole job.

The suspend trap: write and read the same field

There are two ways to suspend a subscription in Plesk and they do not write the same field.

What you doXML gen_info.statusREST subscription status
nothing (active)0active
PUT /domains/{id}/status to suspended0suspended
CLI subscription --webspace-off1suspended
XML <webspace><set> with status 1616suspended

Read the second row again. Suspending over REST leaves the field that the bulk poll reads at zero. So a hosting product that suspends over REST and polls over XML will show the account as active while it is, in fact, suspended. Nothing errors. Nothing goes red. Your panel and your server simply disagree, and the first person to notice is a customer who cannot reach a site you believe is running.

The rule: suspend and resume through the same field you read. Provibr uses XML <webspace><set><gen_setup><status>, and after that both views move together: the XML field reports 16 and REST reports suspended.

gen_info.status is a bitmask, and the values we measured are 0 for active, 1 for webspace-off and 16 for disabled by the administrator. Anything that is not zero counts as suspended, which is the only reading that survives a value we have not seen yet.

Two datasets we never ask the panel for

Plesk's XML API is built for administration, so two of its datasets return credential material to a caller that asks for them:

  • <webspace><get> with the hosting dataset returns the account's FTP password in clear text, with a field explicitly marking it as plain.
  • <customer><get> with the gen_info dataset returns the customer's password hash (bcrypt) alongside the hash type.

That is reasonable for an administrative API. It is not something a hosting platform should be pulling across the wire on a schedule.

A redaction layer is the wrong answer to this. The right answer is not to ask. Provibr's poll requests exactly four datasets (general info, limits, statistics and subscriptions), and we went through them field by field to confirm none carries a secret. The client list comes from REST, which has no password field at all.

There is a design consequence worth stating. A subscription's FTP or system user would make an attractive short identifier, but it lives only in the hosting dataset, so using it as the identity would mean fetching every customer's password on every poll cycle. Hence the domain instead.

The identity of a subscription is a domain name

In Plesk, a subscription is named after its main domain. That is what the panel shows, what its API filters on, and what any integration has to store.

This is a real difference from cPanel and DirectAdmin, where an account has a short username and a domain separately, and it has one practical consequence: the domain cannot be changed afterwards. Whatever your billing system stores as the identifier of a Plesk subscription is a domain name, and a customer who wants to rename their site is asking for a new subscription rather than an edit.

Two smaller things measured along the way. Plesk's own specification says a client password is 5 to 14 characters; a 22-character password was accepted without complaint, so the documented limit is not the enforced one. And Plesk chooses the IP address itself from its own pool, which is why a hosting account in Provibr has an empty address field until the panel reports one.

A failed order can leave half a subscription behind

Measured: creating a domain with a service plan that does not exist answers HTTP 404 with a clear message, and the domain object exists afterwards anyway, with no hosting type and an empty document root. Plesk creates the object first and applies the plan second, and does not roll back when the second step fails.

The customer paid, the order failed, and the next retry now fails differently, because the domain is already in use, by an account they never received. So look the plan up before you create anything, from the plan list you already fetched, and clean up after any failed create. For contrast, a field validation error such as a duplicate login comes back as HTTP 400 and leaves nothing behind: that check runs before creation, and only the plan lookup happens too late.

No lag, which is rarer than it should be

DirectAdmin's usage listing runs about three seconds behind reality in both directions, which forces a second authoritative call. Plesk does not. We measured a create and a delete against the bulk read:

text
    0 ms  create
 4519 ms  create returns 201
 4957 ms  bulk read sees the subscription      <- immediately
 4957 ms  delete
12622 ms  delete returns 200
13001 ms  bulk read no longer sees it          <- immediately

Plesk is transactional: once the call answers, the change is visible in the bulk read. One authoritative source, no second call, no waiting.

The timings themselves are worth noting. Creating a subscription takes 4.1 to 4.5 seconds and deleting one 7.7 seconds, long enough that a customer watching an order form needs to be told something is happening. Switching plans takes 1.5 seconds and suspending 0.78.

What Provibr does with all of this

Provibr sits between your billing and your Plesk server. You connect the server with one API key, and after that a subscription is an ordinary row: order it, suspend it, resume it, move it to another service plan, delete it.

  • One key, because the same key opens both APIs.
  • Suspension goes through the field the poll reads, so panel and server cannot drift apart.
  • The poll never requests a dataset containing a password, so no customer secret reaches the platform.
  • The plan is resolved before the create, and a failed create is cleaned up rather than left behind.
  • Nothing is trusted on its own word. Every mutation ends by reading the subscription back.
  • The agent dials outward from your network, so port 8443 does not face the internet and nothing is installed on the Plesk server.
  • Subscriptions you did not order through Provibr are left alone until you import them, which makes a server with two hundred existing customers adoptable rather than a rebuild.

What it does not do

It manages subscriptions on a Plesk server you operate, not the server itself: services, DNS zones, mail routing and the panel's own settings stay where they are. There is no console, because a hosting account is not a machine. Mailboxes, databases and DNS inside a subscription belong to Plesk and to your customer.

One honest gap: everything here was measured with an administrator key. Whether a reseller or customer-scoped key can use the XML API at all is untested, so the integration probes at connection time and refuses credentials that can only see part of a panel rather than quietly managing half of it.

Measured against Plesk Obsidian 18.0.80 in August 2026, with all responses recorded as fixtures. Behaviour on a reseller-scoped key, on more than one subscription per client, and under real traffic load was not measured, and is noted as such above.

Start with one host and one agent

Create an account, install the agent with a single command and connect your first Proxmox host. The trial runs for 14 days and asks for no payment details.