How Automated Provisioning Works#

SynergyCP does not decide which server a customer receives. Your billing software does, by querying the SynergyCP API for a server whose parts and IP group match what the customer ordered. This page explains that flow and the controls you have over it. It applies to any integration that provisions through the API, including our WHMCS module.


The provisioning flow#

When a customer’s order is accepted, the integration runs the following steps against the SynergyCP API:

  1. Find a matching server. The integration lists servers with the available filter plus the billing IDs of the CPU, memory, drives, add-ons, and IP group the customer selected. With exact part matching enabled, a server only matches if it has those parts and nothing extra in its drive bays and add-on slots. The first result is used.
  2. Find a matching IP entity. The integration looks for an unassigned IP entity in the matching IP group with the billing ID of the customer’s IP block choice.
  3. Provision. If both are found, the integration creates the client account (or reuses an existing one), assigns the server and IP entity, sets the server’s billing ID to the service ID in the billing software, queues any pre-install and OS install, and sets the port speed.
  4. Fall back to a ticket. If either the server or the IP entity is missing, nothing is provisioned. The integration opens a support ticket on behalf of the client containing the order details so staff can fulfil it manually.

The whole flow is automatic. On many operating systems the customer has SSH access within minutes of ordering.


What “available” means#

A server is available for provisioning when all of the following are true:

  • It is in inventory, meaning no client has access to it.
  • It has a valid MAC address recorded, so it can be PXE booted for the OS install.
  • It is not on provisioning hold (see below).

Servers that fail any of these checks are excluded from the availability query. They still appear in the admin panel and in unfiltered API listings, so a server being visible in your inventory does not by itself mean it can be provisioned.

The same availability query is used in two other places by the WHMCS module:

  • The Check Inventory button on a service in WHMCS admin, which reports whether a matching server and IP entity exist right now.
  • Stock synchronization, which sets the WHMCS product quantity to the number of available servers matching that product’s preset billing IDs. This runs from the WHMCS cron.

Specifying parts#

Part matching is driven entirely by billing IDs. Each part definition in SynergyCP (a CPU model, a RAM kit, a drive model, an add-on) carries a billing ID, and the billing software sends the billing IDs from the order as filters on the server list. A server matches when its assigned parts carry those billing IDs.

Filter parameters#

Parameter Part type Notes
cpu_billing CPU Single value.
mem_billing Memory Single value.
disks_billing[0], disks_billing[1], … Drives One entry per drive bay, index is the bay position starting at 0.
addons_billing[0], addons_billing[1], … Add-ons One entry per add-on slot, index starting at 0.
ip_group_billing IP group Matches the group of the server’s ports. Accepts a comma-separated list.
parts[exact]=1 Turn on exact matching (see below).

Every part filter also has a by-ID form (cpu, mem, disks[n], addons[n]) that takes the SynergyCP part ID instead of a billing ID. Billing software should use the billing ID forms so that part definitions can be changed in SynergyCP without touching the billing side.

Matching rules#

  • Comma-separated values are alternatives. cpu_billing=cpu-1270v6,cpu-1280v6 matches a server with either CPU. This is how a single product can be fulfilled from several equivalent part definitions.
  • Billing IDs are compared case-insensitively. HDD-1 and hdd-1 are the same filter.
  • Drive and add-on filters are positional. disks_billing[0] must match the drive in the server’s first bay, disks_billing[1] the second, and so on. The bay order is the order in which drives are assigned to the server in SynergyCP, so it must agree with the order the billing software sends. An order with a 1 TB drive in bay 1 and a 2 TB drive in bay 2 will not match a server that has them the other way round.
  • Omitted filters are wildcards. If no mem_billing is sent, servers with any memory match. If only disks_billing[0] is sent, bays 2 and up are unconstrained unless exact matching is on.

Exact matching#

Without parts[exact]=1, a filter says “at least these parts”. A server with three drives matches an order for one drive as long as bay 1 is right. That is rarely what you want when selling servers, because the customer gets more hardware than they paid for.

With parts[exact]=1, the server must additionally have no drive in the bay after the last one listed and no add-on in the slot after the last one listed. In practice this means:

  • An order listing two drives matches only servers with exactly two drives.
  • An order listing no add-ons matches only servers with no add-ons. A server carrying a hardware RAID card or extra NIC as an add-on can only be sold by a product that lists that add-on.
  • CPU and memory are single-slot, so exact matching does not change how they match.

The WHMCS module always sends parts[exact]=1. If you are writing your own integration you almost certainly want it too.

Worked example#

A server has CPU CPU-1, memory RAM-2, one drive HDD-1 in bay 1, and one add-on AddOn-1. The following queries all include available=1 and parts[exact]=1.

Filters Matches? Why
cpu_billing=CPU-1&mem_billing=RAM-2&disks_billing[0]=HDD-1&addons_billing[0]=AddOn-1 Yes Every slot accounted for.
cpu_billing=CPU-1&mem_billing=RAM-2&disks_billing[0]=HDD-1 No Exact matching requires add-on slot 1 to be empty, and it is not.
cpu_billing=CPU-1,OTHER&mem_billing=RAM-2&disks_billing[0]=HDD-1&addons_billing[0]=AddOn-1 Yes CPU-1 is one of the alternatives.
disks_billing[0]=HDD-1&disks_billing[1]=HDD-1&addons_billing[0]=AddOn-1 No Bay 2 is empty on this server.

Empty drive bays in WHMCS#

In the WHMCS module, a Drive Bay configurable option whose billing ID is None is dropped before the query is built. Because the remaining bays are re-indexed from 0, None should only be used for the trailing bays of a product. Putting None in bay 1 and a drive in bay 2 will query for that drive in bay 1.


Provisioning hold#

Provisioning hold requires SynergyCP 5.6 or newer. On older versions the checkbox is not present and the API field is ignored.

Provisioning hold lets you keep a server in inventory while preventing any integration from provisioning it. Typical uses are a server that is undergoing hardware repair, one you are reserving for a specific customer, or one that has failed a burn-in test and should not go out until it is checked.

While the hold is set:

  • The server is excluded from availability queries, so automated provisioning, the Check Inventory button, and stock synchronization all treat it as if it does not exist.
  • The server remains in inventory and is unchanged in every other way. Staff can still view it, edit it, run OS reloads, and assign it to a client manually.
  • Clearing the hold returns the server to the available pool immediately. No re-sync is needed.

Setting the hold#

  1. In the SynergyCP admin panel, open the server and go to its edit page.
  2. Tick Provisioning Hold and save.

To release the server, untick the box and save.

Via the API#

The flag is exposed on the server as provisioning_hold. It can be read from the server detail endpoint and toggled with a PATCH request:

# Read the current value
curl -sS --get "https://api.example.com/server/8" --data-urlencode "key=$API_KEY" \
  | jq '.data.provisioning_hold'

# Place the server on hold
curl -sS -X PATCH "https://api.example.com/server/8?key=$API_KEY" \
  -d provisioning_hold=1

# Release the hold
curl -sS -X PATCH "https://api.example.com/server/8?key=$API_KEY" \
  -d provisioning_hold=0

Verifying the effect#

You can confirm a held server is excluded by running the same availability query an integration uses. The held server should be absent from the results.

curl -sS --get "https://api.example.com/server" \
  --data-urlencode "key=$API_KEY" \
  -d available=1 -d per_page=100 -d force_refresh_count=1 \
  | jq '.data | {total, ids: [.data[].id]}'

force_refresh_count=1 bypasses the cached result count so total reflects the current state. Without it the count may lag behind the returned rows for a short time.


Building your own integration#

The WHMCS module is a thin client over the public API, and any billing panel can drive the same flow. Everything below uses the API key of an Integration created under System > Integrations, passed as the key query parameter. Full endpoint reference is in the API documentation.

Integration permissions#

The Integration needs at least: Clients (view and edit), Installs (view and edit), Servers In Inventory (view and edit), Servers In Use (view and edit), IP Entities (view), and IP Groups (view).

Step 1: find a server#

Query the server list with available=1, parts[exact]=1, and the billing IDs of the ordered build, as described under Specifying parts. Request one result.

curl -sS --get "https://api.example.com/server" \
  --data-urlencode "key=$API_KEY" \
  -d available=1 -d "parts[exact]=1" -d per_page=1 \
  -d cpu_billing=cpu-1270v6 -d mem_billing=mem-32gb \
  -d "disks_billing[0]=ssd-1tb" -d "disks_billing[1]=ssd-1tb" \
  -d ip_group_billing=loc-la

If data.data is empty, stop here and hand the order to staff. Do not create the client yet, so that a failed order leaves nothing behind in SynergyCP.

Step 2: check for an IP entity#

Confirm an unassigned IP entity of the ordered size exists in the same IP group. This is optional, since the provision call in step 4 selects one itself, but checking first lets you fail cleanly before touching anything.

curl -sS --get "https://api.example.com/entity" \
  --data-urlencode "key=$API_KEY" \
  -d "group[billing]=loc-la" -d billing_id=ip-29 -d server=none -d per_page=1

Step 3: find or create the client#

Look the client up by the billing ID you assign them (the customer’s ID in your billing panel) and create them if absent. The billing ID is what later lets you find the client without storing SynergyCP IDs on your side.

# Look up
curl -sS --get "https://api.example.com/client" \
  --data-urlencode "key=$API_KEY" -d billing_id=1042

# Create
curl -sS -X POST "https://api.example.com/client?key=$API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","first":"Jane","last":"Doe","billing_id":"1042","password":""}'

An empty password creates an account that cannot log in directly. Customers reach SynergyCP through single sign-on from your panel instead (see below).

Step 4: provision#

One POST to server/provision does the rest: it grants the client access to the server, assigns the IP entity, sets the port speed, records the billing ID, and queues the OS install.

curl -sS -X POST "https://api.example.com/server/provision?key=$API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "server_id": 8,
    "client_id": 17,
    "ips_billing": "ip-29",
    "pxe_profile_billing": "os-ubuntu2404",
    "port_speed_billing": "speed-1000",
    "nickname": "web01.customer.example",
    "password": "generated-root-password",
    "billing": {"id": "5531", "max_bandwidth": 20000},
    "access": {"pxe": true, "ipmi": true, "switch": true}
  }'
Field Purpose
server_id, client_id The server from step 1 and the client from step 3.
ips_billing Billing ID of the IP block size. An available entity in the server’s IP group is chosen automatically. Alternatively pass entities as an array of entity IDs to choose them yourself.
pxe_profile_billing Billing ID of the OS reload profile to install. pxe_profile_id is accepted instead.
port_speed_billing Billing ID of the switch port speed. port_speed_id is accepted instead.
nickname Shown as the server name to the client. Usually the hostname.
password Root or Administrator password for the install.
billing.id Your service ID for this server. Must be numeric and unique per Integration. This is the link used by suspend, unsuspend, and terminate.
billing.max_bandwidth Monthly bandwidth allowance in the same unit your panel bills in. Omit for unmetered.
access.pxe, access.ipmi, access.switch Which controls the client may use.

The response contains the provisioned server under data. If you need a pre-install step such as a quick format before the OS, run it as a separate install first and chain the OS install to it by passing the first install’s ID as parent.id when you POST server/{id}/install for the OS. This is what the WHMCS module does with its Pre-OS Install setting.

Step 5: keep the two systems in sync#

Every later action locates the server by its billing ID, so store nothing from SynergyCP in your panel beyond that.

# Find the server for service 5531
curl -sS --get "https://api.example.com/server" --data-urlencode "key=$API_KEY" -d billing_id=5531
  • Suspend and unsuspend. The client’s access record controls this. List server/{id}/access?is_primary=1 to find it, then PATCH server/{id}/access/{access_id} with {"is_active": false, "suspension_reason": "..."} to suspend or {"is_active": true} to restore. Pass "auto": true when the suspension is triggered by your panel rather than by a person. VIP clients are protected from automatic suspension and the API will report the request as ignored.
  • Terminate. PATCH server/{id} with {"wiped": "1"} returns the server to inventory. Add "auto": true for the same VIP protection.
  • Single sign-on. Redirect the customer to GET auth/sso?key=<client-api-key>, optionally with view_type=server&view_id=<server id> to land on a specific server. Create the client’s API key once with POST key and the body {"owner_type": "client", "owner_id": <client id>}, then reuse the returned key.
  • Bandwidth usage. List server?integration_id=me and read each server’s usage field, which carries used and max in bits for the current period. Match servers back to services by billing.id.
  • Stock levels. Periodically run the step 1 query for each product with force_refresh_count=1 and use total as the sellable quantity. Servers on provisioning hold are already excluded.

Troubleshooting a failed provision#

If an order produced a ticket instead of a server, work through these in order:

  1. Run the availability query above with the billing IDs from the order added as filters (cpu_billing, mem_billing, disks_billing[0], addons_billing[0], ip_group_billing). If nothing is returned, no server currently matches.
  2. Check the server you expected to match. Confirm it has no client attached, has a MAC address recorded, and is not on provisioning hold.
  3. Compare part billing IDs. Every part on the server must carry a billing ID that matches the order, and the drive bay order must match between SynergyCP and the billing software.
  4. Check the IP entity. There must be an unassigned entity in the matching IP group with the ordered block’s billing ID.
  5. Check the integration’s permissions. It needs view and edit on Servers In Inventory, Servers In Use, Installs, and Clients, plus view on IP Entities and IP Groups.