Use the ZYNOO API to order VPS hosting and manage servers from your own application. You can also manage existing VPSs without placing orders through the API.
Base URL
https://bill.zynoo.com/vps-api/index.php
Make your first request
Create an API credential under API Access in your reseller account.
Use HTTPS and send request bodies as JSON. Responses contain ok: true on success, or ok: false with an error object.
Every request except /v1/ping needs the authentication headers. Examples on the following pages show the URL and body; add those headers to your request. Response examples show selected fields. IDs, prices and addresses are illustrative.
Check the API
A public availability check. No API credential is required.
Your API Access ID identifies your account. Your secret signs each request; it is never sent to the API. Make calls from your backend and keep both values out of browser code.
Required headers
Header
What to send
X-VR-Key
Your API Access ID.
X-VR-Timestamp
Current Unix time in seconds. Keep your server clock accurate; the default tolerance is five minutes.
X-VR-Nonce
A fresh random string for each request: 12–120 letters, digits, or ._~-.
X-VR-Signature-Version
2
X-VR-Signature
The hexadecimal HMAC-SHA256 signature described below.
Content-Type
application/json when sending a body.
Use X-Idempotency-Key for safe retries of orders, power actions and reinstalls. Save one unique key per logical action; reuse it only for that same action. Use 1–128 letters, digits, or ._:-.
Create the signature
Join these six values with a single LF newline (\n) between them, then calculate HMAC-SHA256 using your API secret:
Use the uppercase method and decoded path, such as /v1/vps. Exclude the domain, /vps-api/index.php and query string. Hash the exact body bytes you send; for GET, hash an empty string. If there is no action key, leave the sixth value empty. Do not append another newline.
Complete PHP example: list your VPSs
Requires PHP 8.1+ with cURL. Set ZYNOO_API_KEY and ZYNOO_API_SECRET in your server environment, then run this example. It makes one read-only request.
Enable the scopes needed by your integration when creating the credential. Each endpoint below lists its scope. If you set an IP allowlist, use the public IP of the server making your API calls.
Plans & orders
Choose a plan, read its available options, then submit an order. Use the values returned by the API for operating systems and other options.
List plans
Get every active reseller catalog plan in your account currency, with prices and provisioning choices. This endpoint is not paginated.
GET/v1/plans
Scope: catalog:read
Example request
GET https://bill.zynoo.com/vps-api/index.php/v1/plans
These read-only examples fetch all plans and print JSON for your billing adapter. Set ZYNOO_API_KEY and ZYNOO_API_SECRET on your backend; the credential needs catalog:read.
Create or update your local products using supplier_pid as the external key.
Keep each offered billing cycle and build option fields from provisioning.
Send that PID and the selected option values when ordering. Refresh one plan with GET /v1/plans/{pid}.
# Python 3, standard library only. Requires catalog:read permission.
import hashlib
import hmac
import json
import os
import secrets
import time
from urllib.error import HTTPError
from urllib.request import Request, urlopen
key = os.environ["ZYNOO_API_KEY"]
secret = os.environ["ZYNOO_API_SECRET"]
path = "/v1/plans"
timestamp = str(int(time.time()))
nonce = secrets.token_hex(18)
canonical = "\n".join([
timestamp, nonce, "GET", path, hashlib.sha256(b"").hexdigest(), ""
])
signature = hmac.new(
secret.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).hexdigest()
request = Request(
"https://bill.zynoo.com/vps-api/index.php" + path,
headers={
"Accept": "application/json",
"X-VR-Key": key,
"X-VR-Timestamp": timestamp,
"X-VR-Nonce": nonce,
"X-VR-Signature-Version": "2",
"X-VR-Signature": signature,
},
method="GET",
)
try:
with urlopen(request, timeout=45) as response:
result = json.load(response)
except HTTPError as error:
failure = json.loads(error.read())
raise RuntimeError(f"HTTP {error.code}: {failure['error']['code']}") from error
if not result.get("ok"):
raise RuntimeError(result.get("error", {}).get("code", "request_failed"))
# Upsert local billing products by supplier_pid; keep every offered cycle.
products = [{
"supplier_pid": int(plan["pid"]),
"name": plan["name"],
"group": plan["group"],
"prices": plan["prices"],
"provisioning": plan["provisioning"],
} for plan in result["plans"]]
print(json.dumps({"currency": result["currency"], "products": products},
indent=2, ensure_ascii=False))
The scripts output product data; your adapter writes it to your billing system. Use the returned currency and prices, and keep your retail markup separate.
Plan options
Replace {pid} with a plan’s pid. The provisioning object lists required fields and valid choices.
GET/v1/plans/{pid}
Scope: catalog:read
Example request
GET https://bill.zynoo.com/vps-api/index.php/v1/plans/10
Use prices to find available billing cycles. Check the required flags for hostname, root_password, operating_system, configurable_options and custom_fields. Base recurring prices already include your discount; option recurring prices are before that discount. Each price is for its full billing cycle. Setup fees are not tier-discounted; the invoice includes the final total and applicable taxes.
Order a VPS
Create an order using a plan ID and its options. Your account must have ordering enabled. Payment or available credit determines when provisioning can proceed.
POST/v1/orders
Scope: services:create
Body field
Value
client_service_key
Required. Your unique, permanent ID for this VPS, e.g. store:42. Use 1–128 letters, digits or ._:-.
pid
Required. Plan ID.
billing_cycle
An offered billing cycle; defaults to monthly.
hostname
If required: a DNS hostname, up to 255 characters.
root_password
If required: 8–128 UTF-8 bytes, without control characters.
operating_system
The selected OS value from the plan.
configurable_options / custom_fields
Objects containing the required field names and their values.
A new order returns 201. Save service.remote_service_id and use Get a VPS to check its status. Pending means it is not ready yet; Active means the service is active.
Your VPSs
Use the remote_service_id from your VPS list in place of {reference}. For example: ZV-ABC-0101. Your original client_service_key is also accepted. URL-encode the reference when building the path.
List your VPSs
Returns the VPS records belonging to your account. The list includes every service status unless you add a filter.
GET/v1/vps
Scope: services:read
Query parameter
Value
status
Optional: active, pending, suspended, terminated or error.
limit
Records per page: 1–250. Default: 100.
cursor
The next_cursor returned by the previous page.
Example request
GET https://bill.zynoo.com/vps-api/index.php/v1/vps?status=active&limit=100
When has_more is true, request the next page using cursor and the same status filter. Service status and power state are separate: an Active VPS can be powered off.
Get a VPS
Get the hostname, IP addresses, service status and latest reinstall operation for one VPS.
GET/v1/vps/{reference}
Scope: services:read
Example request
GET https://bill.zynoo.com/vps-api/index.php/v1/vps/ZV-ABC-0101
Read up to 100 VPS records in one request. Results are keyed by the reference you send. Missing or unowned references appear in errors with service_not_found.
POST/v1/vps/query
Scope: services:read
Example request
POST https://bill.zynoo.com/vps-api/index.php/v1/vps/query
Content-Type: application/json
{
"references": [
"ZV-ABC-0101"
]
}
Resource readings normally refresh every 30 minutes. Use collected_at and cache_seconds to show their age. A new VPS may need a minute or two before readings become available. Display unavailable or null values as unavailable, not zero.
Refresh power state
Get a fresh online/offline reading. This does not force new CPU, memory or disk readings.
GET/v1/vps/{reference}/metrics/refresh
Scope: services:read
Example request
GET https://bill.zynoo.com/vps-api/index.php/v1/vps/ZV-ABC-0101/metrics/refresh
The response uses the same metrics object. power_state is online, offline, suspended or unknown; status_collected_at is its timestamp. Timestamps use server_timezone.
Older service references
Get a ZYNOO reference
Convert an older reference to the current ZYNOO format. Send {}. Returns service.remote_service_id; an existing ZYNOO reference is returned unchanged. Requires services:read and services:reference:write.
POST/v1/vps/{reference}/reference
Scope: services:reference:write
Example request
POST https://bill.zynoo.com/vps-api/index.php/v1/vps/ZV-ABC-0101/reference
Content-Type: application/json
{}
Power & access
Use these requests to control a VPS or change its password. Your credential needs the listed scope, and your account must allow VPS management. Check customer ownership in your application before sending a command.
Power controls
The VPS must be Active, without a suspension hold or an unfinished reinstall. Send {} and an X-Idempotency-Key for the action. See cooldown and retry rules.
Endpoint · scope: power:write
Action
POST /v1/vps/{reference}/start
Start the VPS.
POST /v1/vps/{reference}/stop
Shut down the VPS gracefully.
POST /v1/vps/{reference}/reboot
Restart the VPS.
POST /v1/vps/{reference}/poweroff
Force the VPS off. Unsaved data may be lost.
Example request
POST https://bill.zynoo.com/vps-api/index.php/v1/vps/ZV-ABC-0101/reboot
Content-Type: application/json
X-Idempotency-Key: reboot-42-1
{}
A successful response acknowledges the command. Call Refresh power state to check the result.
Change the root password
Set a new password for an Active VPS without holds or an unfinished reinstall. Use 8–128 UTF-8 bytes without control characters.
POST/v1/vps/{reference}/password
Scope: password:write
Example request
POST https://bill.zynoo.com/vps-api/index.php/v1/vps/ZV-ABC-0101/password
Content-Type: application/json
{
"password": "REPLACE_WITH_PRIVATE_PASSWORD"
}
Returns 200 with ok: true and a service object. The password is not returned.
Suspend or terminate a VPS
Scope: lifecycle:write. Send {} to each endpoint. These actions return 200 with ok: true and the updated service.
Endpoint
Action
POST /v1/vps/{reference}/suspend
Suspend the VPS.
POST /v1/vps/{reference}/unsuspend
Remove your suspension. A ZYNOO hold must be resolved separately.
POST /v1/vps/{reference}/terminate
Permanently delete the VPS and its data. Confirm with the customer first.
OS reinstall
Reinstalling a VPS replaces its operating system and deletes its existing data. Ask the customer to confirm before continuing. The service must be Active, without holds or an unfinished reinstall, and reinstall must be enabled for your account.
1. Choose an operating system
Get the operating systems available for this VPS. Use the returned value in your reinstall request; it may differ from the value used when ordering. Your account must allow VPS management.
GET/v1/vps/{reference}/reinstall-options
Scope: services:read
Example request
GET https://bill.zynoo.com/vps-api/index.php/v1/vps/ZV-ABC-0101/reinstall-options
The password must contain 8–128 UTF-8 bytes without control characters. confirmation_hostname must match the current hostname after trimming whitespace. Save your action key with this request.
Check JSON formatting, the 64 KiB body limit and Content-Type.
401
Check the access ID, signature and server clock.
403
Check credential scopes, account permissions and allowed source IPs.
404
Check the endpoint and VPS reference.
409
Read error.code: the VPS state, an action key, an existing operation or a reused nonce may conflict.
422
Correct the fields named in the error.
426
Use HTTPS.
429
Wait for Retry-After before sending another request.
5xx
An API or upstream error occurred. Keep the request ID and check the outcome before repeating a write.
Rate limits
All credentials for one reseller share 120 requests per minute. Each credential also has a configured limit, which may be lower. X-RateLimit-Limit describes the shared allowance; X-RateLimit-Scope: reseller identifies its scope. Use X-RateLimit-Remaining and Retry-After to pace requests.
For 429 rate_limit_exceeded, wait for Retry-After seconds. Use POST /v1/vps/query for up to 100 service records at once, and reuse cached metrics.
Cooldowns & retries
A timeout does not establish whether a write completed. Check the VPS or operation first. For a retry, retain the action key and payload, but generate a new timestamp, nonce and signature.
Condition
Response and next step
Power cooldown
120 seconds per VPS, shared across start, stop, reboot and poweroff and all credentials. 429 power_action_cooldown includes Retry-After and error.details.retry_after_seconds. Wait before submitting another power command.
Operation in progress
409 power_action_in_progress or service_operation_in_progress: check status and wait; use Retry-After when present. Processing can outlast the power cooldown.
Reinstall cooldown
Five minutes between new reinstall requests.409 reinstall_cooldown does not include Retry-After; wait until five minutes after the last submission.
Unresolved reinstall
409 operation_unresolved: poll the existing operation. running or accepted means wait; unknown requires supplier review. An expired cooldown does not unblock an unresolved reinstall.
Example: power cooldown response
HTTP response
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 90
{
"ok": false,
"error": {
"code": "power_action_cooldown",
"message": "A power command was recently submitted. Wait before submitting another power command.",
"details": {
"retry_after_seconds": 90,
"cooldown_seconds": 120
},
"request_id": "example-request-id"
}
}
Use the returned wait time for your interface countdown. Read-only status checks remain available, subject to the API request limit.
Retry behavior by action
Action
Reuse the original action key
Order
Keep client_service_key and the same order payload. A replay returns the existing service with 200, rather than creating another order.
Power command
The same key, VPS and action returns the previous successful result without sending the command again. For 409 power_action_outcome_unknown, check the VPS or contact support; do not generate a new key to force a resend.
Reinstall
The same key and payload returns the stored operation with 202. Read its state; 202 is not confirmation that the reinstall succeeded.
Password / lifecycle
These actions have no action-key duplicate protection. Check the service after a timeout before deciding whether to repeat the request.
409 idempotency_conflict means the key belongs to a different request. 409 auth_replay_detected means the authentication nonce was reused; create fresh authentication headers.