Reseller API
Sell our hosting and domains under your own brand. Order, pay from your balance and manage everything from your own software.
Getting started
- API access is enabled per customer: it opens automatically once your account balance reaches the minimum balance set by our team, and our team can also enable it for you regardless of balance (open a support ticket). Access is enabled per customer.
- Add funds to your account balance. Every API order is paid only from your available balance (balance minus your minimum balance).
- In your client area open API access and create a key. Give each key a name (up to 5 active keys); optionally restrict it to your server's IP addresses. Our team can also generate a key for you from your customer profile.
- Call the API with the key. Base URL:
https://syllhost.com/api/v1
Authentication
Send your key in the Authorization header (or X-API-Key). Always use HTTPS; plain HTTP requests are rejected.
curl -H "Authorization: Bearer sh_live_xxxxxxxxxxxxxxxx" https://syllhost.com/api/v1/account
- Keys are shown once when created and are stored only as a hash. If a key leaks, revoke it in your client area; it stops working immediately.
- Keep keys on your server. Never put them in a website's JavaScript or in a mobile app.
- A key can be limited to specific IP addresses. Requests from other IPs get
403 ip_not_allowed.
Conventions & errors
- JSON in, JSON out. Successful responses look like
{"data": ...}; lists add"meta": {"page","per_page","total","last_page"}(use?page=and?per_page=, max 100). - All money is a string in USD with two decimals, for example
"8.00". - Dates are ISO 8601.
Errors always use the same shape and a meaningful HTTP status:
{ "error": { "code": "insufficient_balance", "message": "Insufficient balance. This order costs $8.00 ..." } }
| HTTP | code | Meaning |
|---|---|---|
| 400 | https_required | Use https://. |
| 401 | invalid_api_key | Missing, wrong or revoked key. |
| 402 | insufficient_balance | Balance (minus your minimum balance) does not cover the order. Nothing was created or charged. |
| 403 | api_not_enabled / ip_not_allowed | API access is off for your account, or the key is not allowed from this IP. |
| 404 | not_found | Unknown plan, service, invoice, order or domain (or it is not yours). |
| 409 | conflict | For example the domain is not available, or an Idempotency-Key was reused for a different order. |
| 422 | validation_failed / order_rejected | Invalid input; fields lists each problem. |
| 429 | rate_limited | Too many requests. Wait for the Retry-After seconds. |
| 503 | api_disabled / unavailable | API temporarily off, or a price is not available right now. |
Balance & pricing
- You buy at retail price minus your reseller discount. The prices returned by the catalogue endpoints already include your discount, and they are exactly what is charged. Your discount can apply to all products or only to selected products:
GET /accountreturnsdiscount_scope("all", or a list of product ids). Domains get the discount only when the scope is"all". Domains are never priced below our cost. - The charge is taken from your account balance in the same step as the order. If your balance is too low, or the balance left after paying would be below your required minimum balance, the order is refused with
402and nothing is created. - Renewals are billed at the price you bought at. Renewal invoices are created before the due date and, with auto-renew on, are paid from your balance automatically. Keep it funded, or pay them yourself with
POST /invoices/{id}/pay. - Use
GET /accountto read your balance, your minimum balance and how much you can spend.
Your customer's billing details
Every order and invoice can carry your end customer's details. Add an optional billing object to the order request and that invoice prints those details instead of yours. Leave it out and the invoice uses your own account details. Renewal invoices for that service keep the same details.
"billing": {
"name": "Acme Traders", // required when "billing" is sent
"company": "Acme Traders Ltd",
"email": "accounts@acme.example",
"phone": "+92.3001234567",
"address1": "1 Main Street",
"address2": "",
"city": "Lahore", "state": "Punjab", "postcode": "54000",
"country": "PK", // 2-letter code
"tax_id": ""
}
For domain registrations the same details become the domain owner (WHOIS registrant), so name, email, phone, address1, city, postcode, country are all required for a domain. Without billing your own account details are used.
Idempotency (no double charges)
Every call that spends money (POST /orders/hosting, POST /orders/domain) must send an Idempotency-Key header: 8-100 characters, unique per order you want to place (an order id from your own system is perfect). If a request times out, send it again with the same key: you get the original order back and are not charged twice. Reusing a key for a different order returns 409.
You can also send a reference (your own order id, up to 100 characters). It is stored and returned with the order and invoice.
Account
GET/account
{ "data": { "name": "Reseller Co", "email": "you@example.com", "currency": "USD",
"balance": "92.00", "minimum_balance": "5.00", "available_to_spend": "87.00",
"discount_percent": "20.00", "discount_scope": "all", "rate_limit_per_minute": 60 } }
GET/transactions your wallet movements (deposits, invoice payments).
Plans & domain prices
GET/products filter by group: ?group=shared-hosting, wordpress-hosting, reseller-hosting or business-email (the group is what distinguishes plan families; type is shared for every hosting plan and other for email plans, so filter by group, not by type)
{ "data": [ { "id": 5, "name": "WP Starter", "type": "shared", "group": "WordPress Hosting",
"requires_domain": true, "in_stock": true, "tagline": "Launch your first WordPress site in minutes",
"resources": { "disk_mb": 10240, "bandwidth_gb": 100, "websites": 1, "email_accounts": 10, "databases": 5, "inodes": null },
"prices": { "monthly": { "price": "1.43", "renewal": "1.43", "setup_fee": "0.00" },
"annual": { "price": "14.25", "renewal": "17.10", "setup_fee": "0.00" } } } ] }
GET/products/{id}
GET/domains/tlds registration, renewal and transfer prices per extension, in your price.
GET/domains/check?domain=example.com
{ "data": { "domain": "example.com", "status": "available", "available": true, "message": "Great news - this domain is available!", "register_price": "11.40" } }
Order hosting
POST/orders/hosting header Idempotency-Key required
| Field | |
|---|---|
product_id | required. From /products. |
cycle | required: monthly, quarterly, semiannual, annual, biennial, triennial (must be offered by the plan). |
domain | the customer's domain (required for plans that need one). |
register_domain | optional boolean: also register domain for the customer (needs complete billing details or your own complete profile). years 1-10. |
billing | optional, see above. |
reference | optional, your own order id. |
curl -X POST https://syllhost.com/api/v1/orders/hosting \
-H "Authorization: Bearer sh_live_xxx" -H "Idempotency-Key: my-order-1001" -H "Content-Type: application/json" \
-d '{"product_id":5,"cycle":"monthly","domain":"acme-traders.com","reference":"CUST-77",
"billing":{"name":"Acme Traders","email":"accounts@acme.example","country":"PK"}}'
{ "data": { "order_number": "ORD-100245", "status": "active", "reference": "CUST-77", "total": "1.43",
"service": { "id": 812, "status": "active", "provision_status": "done", "plan": "WP Starter", "type": "shared",
"domain": "acme-traders.com", "cycle": "monthly", "price": "1.43", "next_due_date": "2026-11-12",
"username": "acmetrad", "server_ip": "203.0.113.5", "nameservers": ["ns1.syllhost.com","ns2.syllhost.com"],
"control_panel_url": "https://host.example.com:2083" },
"invoice": { "number": "INV-100311", "status": "paid", "total": "1.43", "billing": { "name": "Acme Traders", ... } },
"balance": "90.57" } }
The account is created right away. If the server is busy, service.status may be pending with provision_status queued or failed: payment is kept and we retry automatically; poll GET /services/{id}. The first cPanel login details are emailed to your account email; use POST /services/{id}/password to set a new password programmatically.
Order a domain
POST/orders/domain header Idempotency-Key required. No hosting plan is needed.
| Field | |
|---|---|
domain | required, for example acme-traders.com. |
type | register (default) or transfer (then epp_code is required). |
years | 1-10 (register). |
billing | the domain owner: name, email, phone, address1, city, postcode, country are required (or your own complete profile is used). |
{ "data": { "domain": { "id": 55, "name": "acme-traders.com", "type": "register", "status": "active", "years": 1,
"expires_at": "2027-10-12T00:00:00+00:00", "renewal_price": "13.60" },
"invoice": { "number": "INV-100312", "status": "paid", "total": "11.40" }, "balance": "76.60", "note": null } }
Availability is checked before you are charged. If the registry needs extra time the status stays pending (the payment is kept and our team is notified); poll GET /domains/{name}.
Services
GET/services (filter ?status=active) GET/services/{id} GET/orders, /orders/{order_number}
POST/services/{id}/password sets a new random cPanel password and returns it once:
{ "data": { "username": "acmetrad", "password": "Kx9...", "note": "Shown once. Store it securely." } }
Email accounts
Every hosting plan includes mailboxes, and email plans add more. Manage the mailboxes of any active hosting service you own. Each mailbox's storage limit follows its email plan — 5 GB (Starter), 20 GB (Standard) or 50 GB (Premium); a hosting plan's own included mailboxes are 1 GB — and it can send up to 100 messages every 24 hours; webmail is at https://mail.syllhost.com (sign in with the full mailbox address and its password).
Mail client settings (same for every mailbox): IMAP mail.<domain> port 993 SSL/TLS, POP3 port 995 SSL/TLS, SMTP port 465 SSL/TLS (or 587 STARTTLS), username = the full mailbox address. If mail.<domain> does not connect, use server.syllhost.com with the same ports. The mailbox list response also returns mailbox_quota_mb and a mail_settings object with these values for your domain.
GET/services/{id}/mailboxes included slots, used and free counts, and the mailbox list:
{ "data": { "service_id": 812, "domain": "acme-traders.com", "slots": 10, "used": 2, "free": 8, "mailbox_quota_mb": 1024,
"webmail_url": "https://mail.syllhost.com",
"mailboxes": [ { "email": "info@acme-traders.com", "suspended": false },
{ "email": "sales@acme-traders.com", "suspended": false } ] } }
POST/services/{id}/mailboxes local (letters, digits, . _ -) and password (min 10 characters, letters and numbers). Creating beyond the included count returns 409 — buy an email add-on or an email plan for more. POST/services/{id}/mailboxes/password DELETE/services/{id}/mailboxes (send local in the JSON body for both).
Email plans are ordered like any hosting plan: pick a plan from the business-email group in GET /products. Ordering one for a domain you already host adds its mailboxes to that hosting account instead of creating a separate account, so the mailboxes above are where you manage them.
Domains
GET/domains GET/domains/{name or id}
Invoices & renewals
GET/invoices (filter ?status=unpaid) GET/invoices/{id} (with line items and the billing details printed on it)
POST/invoices/{id}/pay pays an unpaid invoice, for example a renewal, from your balance (same minimum-balance rule). Paying twice is safe.
Limits & security
- Rate limit: set per account (default 60 requests per minute per key). Over the limit you get
429and aRetry-Afterheader. - Repeated invalid keys from one IP are blocked for a while (
429 too_many_requests). - We log each request (method, path, status, IP, time), never request bodies or keys. You can see recent requests in your client area.
- Use IP restrictions on your keys, rotate keys if anyone who knew them leaves, and keep the account balance only as high as you need.
Code examples
PHP
$ch = curl_init('https://syllhost.com/api/v1/orders/hosting');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SYLL_KEY'), 'Content-Type: application/json', 'Idempotency-Key: order-1001'],
CURLOPT_POSTFIELDS => json_encode(['product_id' => 3, 'cycle' => 'monthly', 'domain' => 'acme-traders.com']),
]);
$res = json_decode(curl_exec($ch), true);
if (isset($res['error'])) { /* handle $res['error']['code'] */ }
Python
import os, requests
r = requests.post("https://syllhost.com/api/v1/orders/hosting", timeout=60,
headers={"Authorization": "Bearer " + os.environ["SYLL_KEY"], "Idempotency-Key": "order-1001"},
json={"product_id": 3, "cycle": "monthly", "domain": "acme-traders.com"})
print(r.status_code, r.json())
Not available yet
These are handled by our team for now (open a ticket): cancelling or terminating a service, changing a plan, nameserver and DNS changes for domains, domain transfer-out and webhooks. Poll the endpoints above to follow the status of orders and domains.