API v1 — stable

Domain Reseller API Reference

An HTTP interface for registering, renewing and managing domains at your reseller tier price, designed to be driven automatically from your own billing system. The WHMCS and WooCommerce modules are built on this same interface.

https://servernet.cloud/en/developers · 2026/08/25

Looking for exit server tunnel user management? That interface has its own reference: Exit Server Tunnel API Reference

1Getting started

  1. Your account must be enabled as a domain reseller. Until then the domain endpoints return an authorization error.
  2. In your client panel, under Security, issue an API token and narrow its scope to the minimum your integration needs.
  3. Add the egress IP of your own server to that token allowlist. A token without an IP restriction is usable from anywhere.
  4. Fund the account. Registrations and renewals settle against your credit at call time; there is no post-paid billing.
  5. Make a test call to the health endpoint and reconcile the returned tier and balance against the panel.
The raw token is displayed once, at issue time. We retain only its cryptographic digest, and recovery is computationally infeasible. A lost token must be revoked and replaced; revocation is immediate and does not affect the other tokens on the account.

2Authentication and scope

Authentication uses a bearer token in the Authorization header. There is no session, cookie or CSRF layer; every request is independent and stateless. The token must never appear in a URL path, a query parameter or browser-side code, where it would be recorded in server logs, CDN logs and browser history.

curl -H "Authorization: Bearer sn_xxxxxxxx" \
     https://servernet.cloud/api/v1/ping

Scopes

readread the account profile, services, invoices and credit balance
domains:readread the domain portfolio, availability and tier pricing
domains:writeregister and renew domains — settles against account credit
domains:managemodify nameservers and the auto-renew flag on existing domains
tunnel:readread the WireGuard-over-TCP accounts of your exit server
tunnel:writecreate and remove tunnel accounts — the private key is returned once and never stored

Write scope implies read scope; the converse never holds. We recommend issuing a separate token per environment — one read-only for reporting, one write and IP-bound for the billing host. A leaked read-only token cannot incur cost.

3Response contract

Every response is a JSON envelope with a fixed shape. Client branching must key on the machine-readable error identifier, never on the message text. Message text is localizable and editable and is not part of the stable contract; identifiers are part of the v1 contract and will not be removed or redefined before a new major version.

{"ok": true,  "data": { ... }}
{"ok": false, "error": "insufficient_credit", "message": "..."}
Do not rely on the HTTP status code alone; always evaluate the ok field. This is not theoretical caution: several upstream services in this same chain, the registrar and the payment gateway among them, return 200 on failure and place the real outcome only in the body. A client that inspects only the status code will read a failure as a success.

4Endpoints

GET /api/v1/ping connection test, tier and credit read
GET /api/v1/tlds per-TLD prices (register/renew/transfer) domains:read
POST /api/v1/domains/check availability and price domains:read
GET /api/v1/domains your domains domains:read
GET /api/v1/domains/{domain} details, expiry, status domains:read
POST /api/v1/domains register — deducted from credit domains:write
POST /api/v1/domains/{domain}/renew renew — deducted from credit domains:write
PUT /api/v1/domains/{domain}/nameservers set nameservers domains:manage
POST /api/v1/domains/{domain}/lock turn transfer lock on domains:manage
POST /api/v1/domains/{domain}/auto-renew auto-renew flag domains:manage

Availability and pricing

POST https://servernet.cloud/api/v1/domains/check
{"domain": "example.com", "tlds": ["com", "net"]}

{"ok": true, "data": [{
  "domain": "example.com", "state": "free", "available": true,
  "currency": "IRT",
  "price": {"register": 1150000, "renew": 1250000, "retail": 1320000},
  "discount_pct": 12.88, "price_floored": false
}]}

Client logic must branch on state, not on the available boolean. The value unchecked means the lookup did not resolve and must be treated as a transient condition eligible for retry, not as taken. Collapsing these six states into one boolean once told users in this very system that their preferred name was gone while it was in fact available.

free registrable premium registrable, at registry premium pricing taken already registered unchecked lookup did not resolve — retryable unsupported the extension is not in our sales catalogue no_price registrable, but no reliable price is available

A true price_floored flag means your tier discount was not fully applied on that extension because the resulting price reached our margin floor. The condition is never hidden; it is described in full in the pricing section.

Registration

POST https://servernet.cloud/api/v1/domains
Idempotency-Key: your-order-12345
{"domain": "example.com", "years": 1,
 "nameservers": ["ns1.you.com", "ns2.you.com"]}

{"ok": true, "data": {
  "domain": "example.com", "status": "pending",
  "order_state": "registered", "registrant": "reseller",
  "charged": 1265000, "currency": "IRT"
}}
order_state
registeredterminal — the domain is registered at the registryno further action
pendingnon-terminal — the order is accepted and queuedpoll the domain endpoint with increasing backoff; do not re-order
manualnon-terminal — held for human review on our sidethe amount is held and the outcome will be reported
failedterminal — registration did not completethe amount is returned to your credit in full
The pending state is not a failure and must not be handled as one. Interpreting it as an error and re-submitting can purchase a domain that is being registered at that very moment. The idempotency key exists precisely to contain this case, and sending one on every billable operation is a correctness requirement.

5Idempotency and retries

The server accepts requests without an Idempotency-Key header, but in that case **no duplicate protection applies whatsoever**; sending one on every billable operation is therefore a correctness requirement of any integration. The key is at most 80 characters and is claimed on a unique database index before any work is performed, so two concurrent requests carrying the same key can never produce two financial transactions. A replayed response is identical in substance to the original and carries a replayed flag. If the first attempt ends in an error the key is released — idempotency means one operation is not performed twice, not that one error is repeated forever.

A renewal key must incorporate the current expiry date

If the key is derived from the domain name alone, the next period renewal of the same domain produces an identical key. The server treats it as a repeat and replays the previous response: your system records success, the customer is charged, and no renewal takes place at the registry. This failure emits no signal until the expiry date. The key must incorporate at least the domain name, the current expiry date and the term in years.

sha256("renew|example.com|2027-01-01|1")

Retry with exponential backoff and jitter, reusing the same key; minting a fresh key on retry defeats the entire guarantee. Our official modules construct and persist this key for you.

6Error identifiers

missing_tokenno Authorization header was sent
invalid_tokenthe token was not recognised
token_expiredthe token has expired — issue a new one
token_revokedthe token has been revoked
ip_not_allowedthe source IP is not on this token allowlist
insufficient_scopethe token lacks the required scope
panel_onlythis operation is not exposed over the API
insufficient_creditinsufficient credit — required and available amounts are in data
daily_cap_reachedthe account daily spend cap is exhausted
already_registeredthe domain is present in our active portfolio
renewal_in_progressa renewal for this domain is already running
request_in_progressa request with this idempotency key is still processing
tld_blockedregistration in this extension is temporarily suspended; nothing was charged
tld_not_soldthe extension is not in the sales catalogue
registrant_incompletethe registrant details on your account are incomplete
no_priceno reliable price is available
lookup_failedthe registrar lookup did not resolve — retryable
registrar_rejectedthe registrar refused the requested change
validation_failedthe request payload is invalid — per-field detail is in data
bad_idempotency_keythe idempotency key exceeds 80 characters
conflictthis idempotency key was already consumed by a different request
invalid_domainthe domain name is not syntactically valid
not_foundthe domain is not in your account
not_registeredthe domain is not yet registered at the registrar — management operations are unavailable
not_activeonly an active domain can be renewed
already_yoursthe domain is already in your own account
account_inactivethe reseller account is unavailable
order_failedthe order did not complete — nothing was charged

7Pricing and tiers

The price returned by the API is your buying price: retail less the tier discount. The tier is derived from two metrics simultaneously — your trailing twelve month purchase volume and your active portfolio size — and is reviewed daily. A quoted price is not an execution guarantee; settlement is authoritative against the fresh quote taken at order time.

  • Upgrades are immediate and apply the moment purchase volume crosses a threshold.
  • Downgrades are gradual: a drop in volume first enters a grace period, then moves at most one step. The asymmetry is deliberate.
  • Your current tier, the distance to the next threshold and the last review date are available in the reseller panel and in the health endpoint response.

The margin floor

Our margin varies per extension and follows the cost structure of the registry concerned. On thin-margin extensions — in practice also the highest demand ones — your tier discount applies only down to a floor that remains above our landed cost. Wherever that floor engages, the API sets price_floored true and returns the effective applied percentage separately.

Disclosing this constraint is a deliberate choice. An undeclared floor produces a discrepancy the client cannot reconcile, discoverable only by manually auditing invoices — precisely the work a loyalty programme is meant to eliminate.

8Quotas and limits

read requests120 / 1 minute
availability and price lookups60 / 1 minute
write operations20 / 1 minute
maximum term per order (years)10
maximum concurrent active tokens20

Exceeding a rate limit returns 429 and must be handled with backoff, not with additional concurrency. Each account additionally carries a daily spend cap, adjustable downward in the reseller panel. This cap is a blast-radius control: in a token compromise scenario it bounds the maximum loss achievable before anyone notices. We recommend setting it to a small multiple of your genuine daily turnover, not higher.

9Deliberately out of scope

The following are not unimplemented; they are deliberately excluded from the token surface. The criterion is explicit: any operation that, with a compromised token, transfers control of an end customer domain is performed only under human authentication in the panel. The transfer lock can be enabled over the API but never disabled — an operation that adds protection is safe, one that removes it is not.

auth_codethe transfer authorization (EPP) code is the bearer credential for domain ownership; returned over an API it persists in your application logs, CDN logs and error trackers.
transfer_unlockdisabling the transfer lock is the necessary first step of moving a domain to another registrar.
registrant_changechanging the registrant is a transfer of legal ownership, not an update of contact details.
dnsDNS record management is outside the scope of this interface; delegate to your own nameservers or DNS provider.
In the current version the registry-side registrant is your reseller account, not your end customer. If your module or code sends customer contact fields, they are ignored and not persisted. Carrying end-customer identity data requires an independent data path — explicit consent, a processing agreement, a retention policy and an erasure route — and until that exists, silently accepting such data would be worse than refusing it.
Iranian national extensions are not offered through this channel. Their cost through an international registrar is many times the direct IRNIC tariff, so a lookup returns unsupported and no price is generated at all. This is a supply constraint rather than a technical one, and it is tracked on the roadmap.

10Official modules

Two reference implementations are maintained against this interface, both downloadable from the reseller panel. If your stack is one of these two, prefer the module over a hand-written integration: the financial safeguards are already implemented in it.

WHMCS modules/registrars/servernet/

A standard registrar module. It installs into the WHMCS registrars directory and requires only the token. It covers lookup, registration, renewal, nameserver management, transfer lock, status synchronisation and price-table import, and constructs the idempotency key itself, expiry date included.

WordPress + WooCommerce wp-content/plugins/servernet-domains/

A WordPress plugin with optional WooCommerce integration. A shortcode renders the search interface; with WooCommerce active the domain enters the cart, the customer pays through your own gateway, and registration executes automatically once payment is confirmed. The plugin also loads cleanly on installations without WooCommerce.

Three financial safeguards the modules implement — and a hand-written integration must implement too
  1. A price is never accepted from the browser. At add-to-cart time the price is re-quoted from this interface and substituted. Without this safeguard a hand-crafted request can place an expensive domain into the cart at an arbitrary amount; settlement still draws the true cost from your credit and the difference is your loss.
  2. Before registration executes, the buying price is compared against the amount quoted at order time. If the increase exceeds a configured tolerance, automatic registration is skipped and the order is held for a human decision. Days can elapse between add-to-cart and payment, and exchange rates move in that window.
  3. The idempotency key is derived from the order line identifier, not the order identifier. One order may contain several domains, and WooCommerce marks a single order paid through several independent paths: the gateway webhook, the customer return, and a manual status change by an administrator.

11Roadmap

The following are planned but not yet published in the v1 interface. Until release, calling the corresponding endpoint returns 404. Adding an endpoint is a backward-compatible change and does not increment the interface version; your client must not error on encountering an unknown field in a response.

transferinbound domain transfer — submitting the authorization code, tracking request state and settling the added year. In development. Note that the TLD listing endpoint already returns a transfer price so you can build a complete price table; the transfer operation itself is not yet callable.
irsupply of Iranian national extensions through a direct connection rather than an international registrar.
webhookevent delivery to an endpoint of yours, removing the need to poll for state changes on non-terminal orders.
contacta per-end-customer registrant data path, with the accompanying consent and erasure mechanics.

Dashboard PDF version