Void API — your domains, over REST.
Check availability, register and renew, switch nameservers, put a domain behind Cloudflare and manage its DNS, security switches and redirects — from your own code, on the same account and balance as the bot.
Quickstart
five calls from zero to a live domainIn the Telegram bot: profile → API → create a key. It is shown once — keep it in a secret store, not in code.
GET /domains/check?domain=… — verified against the registry, with the exact price you will pay.
POST /domains — the balance is charged, the name lands on our nameservers with a parking page.
POST /domains/{id}/cloudflare — zone created, nameservers switched at the registry for you.
POST /domains/{id}/dns — an A record to your server, proxied or not.
export VOID_KEY="void_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
API=https://api.voidhosting.org/v1
# 1. is it free, and for how much?
curl -s "$API/domains/check?domain=example.xyz" -H "Authorization: Bearer $VOID_KEY"
# 2. register (charges the balance, returns 201 + the domain object)
curl -s -X POST "$API/domains" -H "Authorization: Bearer $VOID_KEY" \
-H "Content-Type: application/json" -d '{"domain":"example.xyz"}'
# 3. connect Cloudflare, then add an A record
curl -s -X POST "$API/domains/$ID/cloudflare" -H "Authorization: Bearer $VOID_KEY"
curl -s -X POST "$API/domains/$ID/dns" -H "Authorization: Bearer $VOID_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"A","name":"@","content":"203.0.113.10","proxied":true}'const API = "https://api.voidhosting.org/v1";
const headers = { Authorization: `Bearer ${process.env.VOID_KEY}`, "Content-Type": "application/json" };
const call = async (method, path, body) => {
const r = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
const j = await r.json();
if (!j.ok) throw new Error(`${j.error.code}: ${j.error.message}`);
return j.data;
};
const check = await call("GET", "/domains/check?domain=example.xyz");
if (check.available) {
const domain = await call("POST", "/domains", { domain: "example.xyz" });
await call("POST", `/domains/${domain.id}/cloudflare`);
await call("POST", `/domains/${domain.id}/dns`, { type: "A", name: "@", content: "203.0.113.10", proxied: true });
}import os, requests
API = "https://api.voidhosting.org/v1"
H = {"Authorization": f"Bearer {os.environ['VOID_KEY']}"}
def call(method, path, **body):
r = requests.request(method, API + path, headers=H, json=body or None, timeout=30).json()
if not r["ok"]:
raise RuntimeError(f"{r['error']['code']}: {r['error']['message']}")
return r["data"]
check = call("GET", "/domains/check?domain=example.xyz")
if check["available"]:
domain = call("POST", "/domains", domain="example.xyz")
call("POST", f"/domains/{domain['id']}/cloudflare")
call("POST", f"/domains/{domain['id']}/dns", type="A", name="@", content="203.0.113.10", proxied=True)Example: from purchase to an A record
every request and response of one real flowOne domain, start to finish: check → register → connect Cloudflare → wait for the zone → add records → tighten SSL. Responses are shortened only where marked …. Set H='Authorization: Bearer void_…' once; every call carries it.
1 · Is it free?
curl -s "https://api.voidhosting.org/v1/domains/check?domain=steamgaloshi.world" -H "$H"
{ "ok": true, "data": { "domain": "steamgaloshi.world", "available": true, "price": "8.00" } }2 · Register
curl -s -X POST https://api.voidhosting.org/v1/domains -H "$H" -H "Content-Type: application/json" \
-d '{"domain":"steamgaloshi.world"}'
HTTP/2 201
{ "ok": true, "data": {
"id": "6f1c2e0e-3f6a-4a4b-9a1d-8f5e9b2c7d10", "domainName": "steamgaloshi.world", "status": "ACTIVE",
"nameservers": ["ns1.voidhosting.org", "ns2.voidhosting.org"],
"price": "8.00", "renewalPrice": "8.00", "renewalYears": 1, "autoRenew": false,
"expiresAt": "2027-08-31T19:45:54.000Z", "createdAt": "2026-08-31T19:45:50.714Z", "blockFlags": [] } }
# the balance went 25.07 → 17.07; the name already resolves to our parking page3 · Connect Cloudflare
ID=6f1c2e0e-3f6a-4a4b-9a1d-8f5e9b2c7d10
curl -s -X POST https://api.voidhosting.org/v1/domains/$ID/cloudflare -H "$H"
HTTP/2 201
{ "ok": true, "data": { "connected": true, "domainName": "steamgaloshi.world", "zoneId": "9b3e…",
"nameservers": ["ada.ns.cloudflare.com", "kip.ns.cloudflare.com"], "zoneStatus": "pending", "nsApplied": true } }
# nsApplied: the registry already points at Cloudflare; zoneStatus turns "active" when Cloudflare notices4 · Add records right away (no need to wait for "active")
curl -s -X POST https://api.voidhosting.org/v1/domains/$ID/dns -H "$H" -H "Content-Type: application/json" \
-d '{"type":"A","name":"@","content":"203.0.113.10","proxied":true}'
HTTP/2 201
{ "ok": true, "data": { "id": "a1f0…", "type": "A", "name": "steamgaloshi.world", "content": "203.0.113.10", "proxied": true, "priority": null } }
curl -s -X POST https://api.voidhosting.org/v1/domains/$ID/dns -H "$H" -H "Content-Type: application/json" \
-d '{"type":"CNAME","name":"www","content":"steamgaloshi.world","proxied":true}'
HTTP/2 201
{ "ok": true, "data": { "id": "b7c2…", "type": "CNAME", "name": "www.steamgaloshi.world", "content": "steamgaloshi.world", "proxied": true, "priority": null } }5 · Poll the zone until it is active
until curl -s -X POST https://api.voidhosting.org/v1/domains/$ID/cloudflare/refresh -H "$H" | grep -q '"zoneStatus":"active"'; do sleep 60; done
{ "ok": true, "data": { "connected": true, "domainName": "steamgaloshi.world", "zoneId": "9b3e…",
"nameservers": ["ada.ns.cloudflare.com", "kip.ns.cloudflare.com"], "zoneStatus": "active", "nsApplied": true } }
# typically 2–20 minutes after step 36 · SSL and switches
# the origin has no certificate yet → flexible; switch to "full"/"strict" once it does
curl -s -X PATCH https://api.voidhosting.org/v1/domains/$ID/cloudflare/settings -H "$H" -H "Content-Type: application/json" \
-d '{"ssl":"flexible","botFightMode":true,"alwaysOnline":true}'
{ "ok": true, "data": { "ssl": "flexible", "developmentMode": false, "underAttack": false, "alwaysOnline": true, "botFightMode": true, "regionBlock": false } }7 · Check the result
curl -s https://api.voidhosting.org/v1/domains/$ID/dns -H "$H"
{ "ok": true, "data": [
{ "id": "a1f0…", "type": "A", "name": "steamgaloshi.world", "content": "203.0.113.10", "proxied": true, "priority": null },
{ "id": "b7c2…", "type": "CNAME", "name": "www.steamgaloshi.world", "content": "steamgaloshi.world", "proxied": true, "priority": null } ] }
dig +short steamgaloshi.world # Cloudflare edge IPs — the origin stays hiddenWhat can go wrong along the way: step 2 → 409 DOMAIN_UNAVAILABLE (someone was faster) or 402 INSUFFICIENT_FUNDS; step 3 → 503 CLOUDFLARE_NO_ACCOUNTS (retry in a few minutes); step 6 before the zone is active → 409 CLOUDFLARE_ZONE_PENDING. Nothing in this flow needs to be undone by hand: failed money-moving calls refund themselves.
Authentication
Every call carries an API key as a bearer token. Keys belong to your account, not to a person: whoever holds one can spend the balance and change your domains. Up to 5 keys per account, each revocable on its own — one per integration is a good habit.
Authorization: Bearer void_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxLifecycle
- Create — bot → profile → API → create a key. The plaintext is shown once and never stored on our side (only a hash).
- Inspect — the same screen lists keys by prefix (
void_cT4dmZf…) with creation and last-use time. - Revoke — delete from the screen; the key fails with
401on the next request.
No key is needed for GET /v1 (an index of resources) and the docs. Everything else answers 401 UNAUTHORIZED without one. HTTPS only — HTTP is redirected before it reaches the API.
Conventions
the same rules everywhereEnvelope
{ "ok": true, "data": … }
{ "ok": false, "error": { "code": "…", "message": "…", "details": … } }Status codes
200 ok
201 created (POST /domains, cloudflare, dns, redirects)
4xx your side — see the error code
5xx ours or upstream — safe to retry laterTypes
| kind | format | example |
|---|---|---|
| money | decimal string, USD, 2 places — never a float | "14.50" |
| time | ISO 8601, UTC | "2027-08-28T17:04:38.000Z" |
| ids | UUID v4 for our objects; Cloudflare's own ids for DNS records and redirects | "0a797218-8ec5-…" |
| domain names | normalised to the registrable form, lowercase, no www. | Example.COM → example.com |
| lists | offset / limit (max 100) in the query; items + total in the response | ?offset=20&limit=20 |
Money-moving calls
POST /domains and POST /domains/{id}/renew charge the account balance immediately. If the registry then refuses the order, the charge is reversed automatically — you never pay for a failed operation. Balance is topped up in the bot; the API is read-only about money.
Retries & idempotency
Reads are safe to retry. A repeated POST /domains for the same name fails with 409 DOMAIN_UNAVAILABLE once the first attempt succeeded, so a retry cannot buy the same domain twice. On a timeout, GET /domains tells you whether the order went through before you try again.
Errors
Error codes are stable strings you can switch on; message is for humans and may change. details is present when there is something structured to say (validation issues, the missing amount).
| HTTP | code | meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | malformed request; details[] has path and message per field |
| 400 | DOMAIN_INVALID | not a valid domain name |
| 401 | UNAUTHORIZED | missing, unknown or revoked key |
| 402 | INSUFFICIENT_FUNDS | balance too low; details = { required, balance, missing } |
| 403 | USER_BANNED | account suspended |
| 404 | DOMAIN_NOT_FOUND · DNS_NOT_FOUND · NOT_FOUND | not on this account / no such endpoint |
| 409 | DOMAIN_UNAVAILABLE | taken, premium, or a zone we do not sell |
| 409 | CLOUDFLARE_ALREADY · CLOUDFLARE_NOT_CONNECTED · CLOUDFLARE_ZONE_PENDING | zone state does not allow the operation |
| 409 | DNS_EXISTS · REDIRECT_EXISTS · REDIRECT_LIMIT | duplicate or limit reached |
| 429 | RATE_LIMITED | over the per-key limit — back off |
| 502 | REGISTRAR_ERROR · CLOUDFLARE_ERROR | upstream failed; retry later, nothing was charged |
| 503 | CLOUDFLARE_UNAVAILABLE · CLOUDFLARE_NO_ACCOUNTS | temporarily out of capacity |
{ "ok": false, "error": { "code": "INSUFFICIENT_FUNDS", "message": "account balance is too low — top up in the bot",
"details": { "required": "16.00", "balance": "5.50", "missing": "10.50" } } }Rate limits
Over the limit you get 429 RATE_LIMITED; wait a few seconds and retry. Availability checks go to the registry in real time — cache answers on your side for a few minutes instead of polling, and prefer /domains/search (one call, all zones) over many single checks.