Whether a domain is available, registration and renewal prices.
Buying from balance
Registration with the same engine and prices as the website, with a spend limit per key.
My domains
Your domains, paid-until dates, nameservers and whether they can be listed for sale.
Sell domains
List domains, change price and description, remove them from sale.
Buy, then list
The order number from the purchase response lists the domain in the marketplace right away, and AI writes the card in Russian and English.
Sales history
Sales, marketplace commission and how much was credited to your balance.
Marketplace search
Public listing search with filters — no token needed.
The API is server-side: your script, bot or service (for example, HQName) calls HQClouds over HTTPS with a token from the dashboard. All amounts are in USD; money is charged to your HQClouds balance.
Quick start
Sign in or sign up to HQClouds. If you plan to buy domains, top up your balance and fill in the domain owner contacts (WHOIS) once: WHOIS contacts.
In the dashboard, open the “API” section and create a key: name, scopes and daily spend limit.
Copy the token — it is shown only once. Keep it in an environment variable or a secrets manager.
The token is shown once at creation. We store only its hash — a token cannot be recovered, only reissued.
Do not put the token into website or mobile app code: the API is meant for server-side calls and CORS is closed for it.
If a token leaks, revoke the key in the dashboard: revocation is immediate.
The request body is JSON (Content-Type: application/json) or a regular form. For PATCH, forms must be application/x-www-form-urlencoded.
Error messages are in English; with an Accept-Language: ru header most of them come in Russian. The code field does not depend on the language — rely on it in your code.
The url links in responses point to site pages in the same language as the messages.
The listing card (headline, description, “good fit for” list) also follows the response language: with ?lang=ru or Accept-Language: ru it is in Russian, otherwise in English. This way you see exactly what a buyer sees in that language.
Key scopes
Scopes are set when the key is created. Give a key only what your software needs.
Scope
What it allows
read
Every key has it. Read access: account and balance, domains, orders, listings, sales, domain checks.
market:write
List, edit and delist domains in the marketplace, and request AI descriptions for them.
domains:register
Buy domains from the balance. Requires a daily spend limit.
Limits
60requests per minute per key. Over the limit — 429 with a Retry-After header; the remainder is in X-RateLimit-Remaining.
$100default daily spend limit — set your own when creating a key. Counted over the last 24 hours.
20domain purchases per hour per key — protection against a runaway script loop.
10valid keys per account: revoked and expired keys do not count.
Purchases run strictly one at a time: if another purchase is in progress you get 429 busy — retry in a few seconds with the same Idempotency-Key.
Premium domains are never sold via the API — only manually in the dashboard.
Public marketplace search: 60 requests per minute per IP.
AI listing descriptions: at most 2 generations at once per account, and the number of generations is limited too. Over the limit you get 429 ai_rate_limited with Retry-After; the listing stays for sale.
Key security
A token with the buy permission is access to your balance. Here is what protects it and how to keep it safe yourself.
Allowed IPs
When creating a key, list the addresses of the servers it works from: IPv4, IPv6 or subnets (for example, 203.0.113.0/24 or 2001:db8::/48), up to 20 entries.
A request from any other address gets 403 ip_not_allowed, even with a valid token: a leaked token cannot be used from someone else’s server. This is an extra layer, not a replacement for keeping the token secret — treat it like a password.
An IP allowlist does not work from Cloudflare Workers and similar platforms without a fixed address of their own: issue a separate key for them without a list, with minimal scopes and a daily limit.
Without a list, the key works from any address. The list cannot be edited: issue a new key and revoke the old one.
Brute-force protection
A failed sign-in is a request without a token, with an invalid, revoked or expired token, or from an address not on the allowlist.
More than 30 failed sign-ins per minute from one IP — 429 too_many_auth_failures until the end of the minute; more than 300 per hour — until the end of the hour.
For IPv6, both the address and its whole /64 network are counted — the network threshold is 10 times higher. If the network is blocked because of neighbours, a key whose IP allowlist includes your address keeps working.
We also detect large-scale guessing from many addresses at once and review it manually.
The response to an invalid token does not depend on whether such a key exists: neither the response nor its timing gives anything away.
Money and key issuance
The daily spend limit of a key with the domains:register scope can be at most $1000. It caps the damage even if the token leaks.
A key with the buy permission can only be issued after entering your account password.
Every new key triggers an email to the account address: name, scopes, limit, allowed IPs and the address it was issued from.
You can issue at most 10 keys per hour.
HTTP
code
Meaning
HTTP 401
unauthorized, invalid_token
The token is missing, invalid, revoked or expired. Counts as a failed sign-in.
HTTP 403
ip_not_allowed
The token is valid, but the key has an IP allowlist and the request came from another address. The address the server saw is in the message and in the ip field.
HTTP 403
scope_required
The key lacks the required scope — issue a key that has it.
HTTP 429
too_many_auth_failures
Too many failed sign-ins from your IP: more than 30 per minute or 300 per hour. Requests from this IP are blocked until the window ends; see Retry-After for the wait time.
HTTP 429
rate_limited
The per-key requests-per-minute limit was exceeded — wait Retry-After seconds.
How to keep keys safe
One key per integration: you can revoke it without breaking the others.
Only the scopes you need: listing lots does not need domains:register, and reading needs nothing but read.
Set allowed IPs for server-side scripts.
Rotate keys regularly: issue a new one (it can have an expiry), switch your program to it, revoke the old one.
Never put the token into website JavaScript, a mobile app or a repository — keep it in environment variables or a secrets manager.
Got an email about a key you did not issue? Revoke it right away and change your password.
Idempotent purchases
The connection can drop mid-purchase, and you will not know whether it went through. That is why every purchase requires an Idempotency-Key header: any unique string up to 100 characters (Latin letters, digits and - _ : .), such as a UUID.
Retrying with the same key is safe: the API returns the result of the first attempt and does not charge you twice.
A new key means a new purchase. Generate the key once per purchase and save it before sending the request.
Timeout, dropped connection or a 5xx response — retry with the same key. A 202 response means the order is with the registrar and the money is reserved: do not buy again, poll GET /domains/{domain}.
A retry response is marked idempotent_replay: true. After a 202, GET /domains/{domain} returns status: pending while the registrar is working and status: failed with refunded: true if registration failed.
The key is unique within the account: a repeat with another API key of the same account (for example, after a key rotation) returns the same purchase marked other_api_key: true. A key longer than 100 characters or with other characters is not trimmed but refused: 422 invalid_idempotency_key.
A repeat after a refusal before the registrar or after 404 not_started is a new attempt: the price and the spend limits are checked again. The Idempotency-Key is bound to the domain from the first attempt: the same key with another domain gives 422 idempotency_key_reused.
Lost the response and do not know the order number — GET /domains/register/{Idempotency-Key}: 200 — the order and its outcome, 202 — the purchase is still running, 404 not_started — it never reached the registrar and nothing was charged (repeat the POST with the same key). This request only reads: it buys and charges nothing.
All responses are JSON in UTF-8. A successful response contains "ok": true. An error has "ok": false and an error object with a machine-readable code and a human-readable message. Amounts are strings with two decimals. Lists return items plus total, page, pages, per_page. The purchase amount in a response is exactly what was reserved or charged: if it is not a whole number of cents, the string carries all its digits.
json
{
"ok": false,
"error": {
"code": "daily_limit",
"message": "Daily spend limit reached: spent $95.00 of $100.00 in the last 24 hours, this purchase is $12.99."
}
}
HTTP
code
Meaning
HTTP 200
already_listed
Not an error: the domain is already listed — a 200 response with the same listing and already_listed: true, nothing is changed.
HTTP 400
invalid_json
The request body is not a JSON object.
HTTP 400
bad_request
Bad request: for example, a malformed parameter in the URL.
HTTP 401
unauthorized
No Authorization: Bearer header.
HTTP 401
invalid_token
The token is wrong, revoked or expired.
HTTP 402
insufficient_balance
Not enough money on the balance — top it up in the dashboard.
HTTP 403
ip_not_allowed
The key has an IP allowlist and the request came from another address (the address is in the ip field).
HTTP 403
scope_required
The key lacks the required scope.
HTTP 403
no_spend_limit
The key has no daily spend limit — it cannot buy.
HTTP 404
not_found
No such API endpoint, domain or listing — or it is not yours.
HTTP 404
order_not_found
No such order — or it is not yours.
HTTP 404
not_started
Lookup by Idempotency-Key: the purchase with this key never reached the registrar and nothing was charged — repeat the POST with the same key.
HTTP 405
method_not_allowed
This endpoint does not support this method.
HTTP 409
registration_failed
The registrar refused after the reservation — the money is back on your balance (refunded: true).
HTTP 409
not_available
The domain is taken or its availability could not be confirmed — no money was charged. A retry with the same Idempotency-Key checks the domain again.
HTTP 409
no_contact
Domain owner contacts (WHOIS) are missing — fill them in once in your dashboard.
HTTP 409
price_above_max
The final price is above max_price — nothing was bought.
HTTP 409
price_changed
The price rose between checking the request and reserving the money — nothing was bought or charged. The new price is in error.price, the checked one in error.confirmed_price.
HTTP 409
premium_not_allowed
Premium domain: manual purchase in the dashboard only.
HTTP 409
…
Other purchase refusals before the registrar — no money was charged, the reason is in message.
HTTP 409
order_pending
The registrar is still processing the order — the domain can be listed once it is registered. Retry after Retry-After seconds.
HTTP 409
order_failed
Registration for the order failed and the money was returned — there is nothing to list.
HTTP 409
domain_not_owned
The domain from the order is no longer in your account: sold, transferred or deleted.
HTTP 409
domain_not_real
The domain was bought in test mode (sandbox) and does not exist at the registrar — it cannot be listed for sale.
HTTP 409
lot_not_active
The listing is delisted or sold — it cannot be edited and no AI description can be requested for it.
HTTP 409
lot_reserved
A buyer is paying for the domain right now — until the reservation ends the listing cannot be edited, delisted or regenerated.
HTTP 409
ai_in_progress
The AI description of this listing is already being generated by another request — check the listing after Retry-After seconds.
HTTP 413
payload_too_large
The request body is larger than 64 KB.
HTTP 422
validation_failed
Invalid data (price, category, the domain cannot be listed, etc.) — details in message.
More than 30 failed sign-ins per minute or 300 per hour from one IP (no token, a wrong token, or a non-allowlisted IP) — requests from that IP are refused until the window ends; the wait is in Retry-After.
HTTP 429
daily_limit, hourly_limit
The daily spend limit or the hourly purchase limit of the key is used up.
HTTP 429
busy
Another purchase is in progress — retry in a few seconds with the same Idempotency-Key.
HTTP 429
ai_rate_limited
AI generation limit, or too many generations at once — retry after Retry-After seconds. The listing stays for sale meanwhile.
HTTP 500
internal_error
Failure on our side. Retry purchases with the same Idempotency-Key.
HTTP 502
ai_failed
The model gave no usable description — the listing keeps its previous text (the AI description if it had one, otherwise the rule-based description); try again later.
HTTP 503
api_disabled, market_disabled
The API or the domain marketplace is temporarily disabled.
HTTP 503
ai_unavailable
AI descriptions are off right now — buyers see the rule-based description.
Endpoints
Paths are relative to the base URL https://hqclouds.com/api/v1.
GET/me
Account, balance and key
Who owns the token, how much money is on the balance, which scopes the key has and how much of the daily limit is already spent. Handy for checking a token before you start.
Whether a domain is available and how much registration and renewal cost. Buys nothing. If the domain is for sale in the HQClouds marketplace, for_sale contains the price and a link.
Scope: read
Parameter
Type
Required
Description
domain
string
yes
Domain name, e.g. example.com. Internationalized (non-Latin) domains are accepted as is.
state: available — free, taken — registered, premium — premium domain (not sold via the API), for_sale — for sale in the HQClouds marketplace, unsupported_tld — we do not register this zone, unknown — could not check, retry later.
Availability is indicative: the final price and availability check happens at purchase.
{
"ok": true,
"domain": "example.com",
"state": "available",
"available": true,
"premium": false,
"price": {"register": "12.99", "renew": "14.99", "currency": "USD"},
"for_sale": null,
"note": "Availability is indicative; the final check happens at purchase."
}
POST/domains/register
Buy a domain
Registers a domain for 1 year and charges the price to your HQClouds balance. Before charging, the same final price check runs as on the website. Purchases are processed one at a time. Buying does not list anything for sale: listing is a separate POST /market/lots request.
Scope: domains:register
Parameter
Type
Required
Description
Idempotency-Key
header
yes
A unique string per purchase (up to 100 characters). See “Idempotent purchases” for details.
domain
string
yes
Domain name.
max_price
number
no
Do not buy if the final price is above this amount, USD: a plain number, for example 15 or 14.99. We recommend always sending it.
years
integer
no
Registration period. Only 1 year is available via the API.
Before your first purchase, fill in the domain owner contacts (WHOIS) in your dashboard once — without them you get 409 no_contact.
201 — the domain is registered and already yours. 202 — the registrar is still processing the order: the money is reserved; do not buy again, but check GET /domains/{domain} every minute or two until status becomes active (the domain is yours) or failed (it did not work out, the money is back on your balance).
409 registration_failed — the registrar refused after the reservation: the money is back on your balance (refunded: true) and the daily limit reservation is released. This outcome is final: nothing will be charged for this request later, and you can start a new purchase of the same domain right away (with a new Idempotency-Key). Other refusals (domain taken, price above max_price) do not charge anything either.
If an attempt reached the registrar, a retry with the same Idempotency-Key returns its outcome marked idempotent_replay: true — 201 with the price, 202 or 409 registration_failed — and charges nothing. A refusal before the registrar (domain taken, balance, max_price, limit) can be retried with the same key.
The order number is order.id (dq_…): it is in every response where the purchase reached the registrar (201, 202, 409 registration_failed) and stays the same when you retry with the same Idempotency-Key. GET /orders/{id} shows its outcome, and POST /market/lots with order_id lists the purchased domain. A refusal before the money is reserved has no order number — there was no order.
One amount: the price is checked when the request arrives (not above max_price) and once more when the money is reserved. If it rose in between, nothing is bought: 409 price_changed with the new price in error.price and the price checked on arrival in error.confirmed_price; nothing is charged. If the new price is not above what the user confirmed, repeat with the same Idempotency-Key; if it is higher, ask the user first. If it dropped, the lower price is charged. price in responses, the order amount and the daily-limit spend are exactly what was reserved or charged.
registration — how the domain was registered: mode live (at the real registrar) or sandbox (the platform’s test mode: the registration is simulated and the domain does not exist at the registrar, although the money is charged from the balance), years — the term (always 1 via the API), real — true only for live. With real: false, do not tell the user the domain is theirs: such a domain cannot be listed for sale (409 domain_not_real).
If the response was lost (timeout, dropped connection) and you do not know the order number, ask GET /domains/register/{Idempotency-Key}: it buys and charges nothing.
A repeat after a refusal before the registrar or after 404 not_started is a new attempt: the price and the spend limits are checked again. The Idempotency-Key is bound to the domain from the first attempt: the same key with another domain gives 422 idempotency_key_reused.
The Idempotency-Key is scoped to the account: a repeat with another API key of the same account (for example, after a key rotation) returns the same purchase marked other_api_key: true instead of buying a second time.
{
"ok": true,
"status": "pending",
"price": "12.99",
"currency": "USD",
"domain_name": "example.com",
"message": "The registrar is still processing the order. Check GET /api/v1/domains/example.com in a few minutes.",
"registration": {"mode": "live", "years": 1, "real": true},
"order": {"id": "dq_5adc8ffc18cbcba5c488dd9e", "status": "pending"}
}
409 Conflict
{
"ok": false,
"error": {
"code": "registration_failed",
"message": "Registration failed, the money was returned to your balance.",
"domain": "example.com",
"refunded": true
},
"registration": {"mode": "live", "years": 1, "real": true},
"order": {"id": "dq_5adc8ffc18cbcba5c488dd9e", "status": "failed"}
}
409 Conflict — the price rose between the checks
{
"ok": false,
"error": {
"code": "price_changed",
"message": "The price changed: $13.99 instead of the confirmed $12.99. Nothing was charged — confirm the new price and buy again.",
"domain": "example.com",
"price": "13.99",
"confirmed_price": "12.99",
"charged": false,
"max_price": "15.00"
}
}
201 Created — test mode (sandbox), not a real domain (domain fields shortened)
{
"ok": true,
"status": "registered",
"price": "12.99",
"currency": "USD",
"registration": {
"mode": "sandbox",
"years": 1,
"real": false,
"note": "Test mode (sandbox): the domain was NOT registered at the registrar — this is a simulated purchase. Do not tell the user the domain is theirs and do not list it for sale."
},
"order": {"id": "dq_5adc8ffc18cbcba5c488dd9e", "status": "registered"},
"domain": {
"id": "svc_9d3a1f0b2c4e6a8b0d1f3e5a",
"domain": "example.com",
"status": "active",
"registration": {"mode": "sandbox", "years": 1, "real": false, "note": "Test mode (sandbox): the domain was NOT registered at the registrar — …"}
}
}
GET/domains/register/{idempotency_key}
How a purchase ended (by Idempotency-Key)
Reconciliation after a lost response: the POST /domains/register response never arrived and you do not know the order number — but you knew the Idempotency-Key before sending. Read-only: this request buys nothing, charges nothing and does not contact the registrar.
Scope: read
Parameter
Type
Required
Description
idempotency_key
path
yes
The same Idempotency-Key that was in the purchase header. It is looked up across the whole account: a purchase made with another API key of the account (for example, before a key rotation) is found too, marked other_api_key: true.
200 — the order exists: order is the same object as in GET /orders/{id} (status pending, registered or failed, refunded, the charged amount price, registration, domain_service, listing). While status is pending, there is a Retry-After header.
202 in_progress — a purchase with this key is still running: ask again after Retry-After seconds and do not start a new purchase.
404 not_started — the purchase never reached the registrar (or was refused before any money was reserved): nothing was charged. Send POST /domains/register with the SAME Idempotency-Key — if the first attempt made it after all, the repeat returns its result instead of buying a second time.
{
"ok": true,
"status": "in_progress",
"idempotency_key": "buy-example-com-20261001",
"retry_after": 5,
"message": "A purchase with this key is still running — ask again in a few seconds. Do not start a new purchase."
}
404 Not Found
{
"ok": false,
"error": {
"code": "not_started",
"message": "The purchase with this key never reached the registrar: nothing was charged and no domain was ordered. Send POST /api/v1/domains/register with the SAME Idempotency-Key — it will not create a second purchase.",
"idempotency_key": "buy-example-com-20261001",
"charged": false
}
}
GET/domains
My domains
All domains of the account, paginated. can_list — whether the domain can be listed for sale right now, list_blocked_reason — why not, listing — the current listing if the domain is already for sale.
Status, paid-until date, nameservers and current listing of one domain. Use the same request to wait for the domain after a 202 response.
Scope: read
Parameter
Type
Required
Description
domain
path
yes
Domain name in the path.
Until the domain is issued after a 202, the response is 200 with "domain": null and status: pending. If registration failed — status: failed and refunded: true, the money is back on your balance. 404 — there is neither the domain nor an attempt to buy it.
registration.real: false — the domain was bought in test mode (sandbox): it does not exist at the registrar.
{
"ok": true,
"domain": null,
"domain_name": "example.com",
"status": "pending",
"message": "The registrar is still processing the order. Check GET /api/v1/domains/example.com in a few minutes.",
"registration": {"mode": "live", "years": 1, "real": true},
"order": {"id": "dq_5adc8ffc18cbcba5c488dd9e", "status": "pending"}
}
200 OK — registration failed
{
"ok": true,
"domain": null,
"domain_name": "example.com",
"status": "failed",
"refunded": true,
"message": "Registration failed, the money was returned to your balance.",
"registration": {"mode": "live", "years": 1, "real": true},
"order": {"id": "dq_5adc8ffc18cbcba5c488dd9e", "status": "failed"}
}
GET/orders
My orders
Domain purchases that reached the registrar, newest first (last 5000): made via the API and on the website. Each items element is an order object, as in GET /orders/{id}.
Scope: read
Parameter
Type
Required
Description
status
string
no
Filter: pending (the registrar is still working), registered (the domain is bought), failed (it failed, the money was returned).
The purchase outcome by the order number from the POST /domains/register response: status pending, registered or failed. It also shows the domain service, the current listing and the next hint: what to do next.
Scope: read
Parameter
Type
Required
Description
id
path
yes
Order number (order.id, dq_…).
status: pending — the registrar is still working and the money is reserved: ask again after Retry-After seconds (such responses carry this header). registered — the domain is bought. failed — registration failed and the money was returned to your balance (refunded: true).
domain_service — the domain in your account; null until the order is registered or if the domain is no longer yours (sold, transferred, deleted). listing — your live listing of this domain. can_list and list_blocked_reason — whether the domain can be listed now and why not.
next — the next step: wait (wait and ask again), list_on_market (list it: the method, path and request body are ready), manage_listing (the domain is already for sale) or null — nothing to do.
Someone else’s or an unknown number returns 404 order_not_found: the API never confirms that other people’s orders exist. The price is what you were charged (or what is reserved), without our cost.
registration — how the domain was registered (mode live or sandbox, years, real), as in the purchase response. A sandbox order has can_list: false — a test domain cannot be listed.
{
"ok": true,
"order": {
"id": "dq_5adc8ffc18cbcba5c488dd9e",
"type": "domain_register",
"domain": "example.com",
"domain_display": "example.com",
"status": "pending",
"refunded": false,
"price": "12.99",
"currency": "USD",
"registration": {"mode": "live", "years": 1, "real": true},
"created_at": "2026-10-01 09:14:40",
"updated_at": "2026-10-01 09:14:40",
"domain_service": null,
"listing": null,
"can_list": false,
"list_blocked_reason": null,
"next": {
"action": "wait",
"method": "GET",
"path": "/api/v1/orders/dq_5adc8ffc18cbcba5c488dd9e",
"retry_after": 60,
"message": "The registrar is still processing the order: ask again in a minute."
}
}
}
GET/market/lots
My listings
Your listings in the domain marketplace, newest first (last 1000). Each items element is a listing object, as in the listing response: with the card and the AI description state ai.
Scope: read
Parameter
Type
Required
Description
status
string
no
Filter: active (for sale or reserved), listed, reserved, sold, delisted, cancelled.
page
integer
no
Page number, starting from 1.
per_page
integer
no
Page size: 50 by default, 100 max.
Listing status: for_sale — for sale, reserved — a buyer is paying an invoice, sold — sold, unavailable — removed from sale. raw_status — the exact status: listed, reserved, sold, delisted (removed by you), cancelled (removed by the system: the domain expired or left your account).
cancel_reason — why the system took the listing down (cancelled only): expiring — the domain expires soon, owner_changed — the domain left the account, service_missing or service_<status> — the domain service is missing or not active. null for other listings. sandbox — the domain was bought in test mode and does not exist at the registrar.
Creates a listing in the domain marketplace. The domain must be in your HQClouds account, active, paid for at least 30 more days and have no pending registrar operations.
Scope: market:write
Parameter
Type
Required
Description
domain
string
no
A domain from your account. Send domain or order_id — one of the two.
order_id
string
no
The order number from the purchase response (order.id): lists the purchased domain without repeating its name. Order still pending — 409 order_pending with Retry-After, failed — 409 order_failed, the domain is no longer yours — 409 domain_not_owned, someone else’s or an unknown number — 404 order_not_found. A domain from a test (sandbox) purchase — 409 domain_not_real: it does not exist at the registrar. The same goes for a lot listed earlier: 409 domain_not_real instead of already_listed, and the lot itself is taken down automatically (cancel_reason: sandbox).
price
number
no
Price in USD: from $5 to $100,000. If omitted — the default price, $50.
category
string
no
Theme — a key from the table below. If omitted, we detect it from the domain name.
tags
array | string
no
Up to 10 tags: an array or a comma-separated string. A tag is letters, digits, spaces or hyphens, up to 30 characters; a leading “#” is removed and “_” becomes “-”. An invalid tag returns 422 invalid_field with the rejected list. If omitted, we derive them from the name (cloud, pay, ai…).
headline
string
no
Headline, up to 160 characters. If omitted, buyers see an auto-generated text in their language.
description
string
no
Description, up to 2000 characters. If omitted — an auto-generated text in the buyer’s language.
describe
string
no
AI description: auto (the default) — if AI descriptions are enabled, we make it in the background within a few minutes; ai — generate it now, in the same request: usually 5–30 seconds, at most ~45 seconds (set a client timeout of 60 seconds). An AI failure, a limit or AI being off does not block the listing: the listing is created and ai has status: failed with the reason code.
metadata
object
no
Your own data, for example a domain score from HQName: up to 20 fields; keys — Latin letters, digits and _ (up to 40 characters); values — a string up to 300 characters, a number, true/false or null. Returned in responses exactly as sent; breaking the rules gives 422 invalid_field.
On sale the marketplace keeps a 10% commission; the rest is credited to your HQClouds balance right away.
Repeating the request for a domain that is already listed returns 200 and the same listing with already_listed: true, unchanged (use PATCH to change it). A new listing returns 201. A script retry will not break anything.
domain or order_id is enough: the price, category, tags and text are filled in automatically. card is the listing card exactly as a buyer sees it in the response language (?lang= or Accept-Language): headline, description, “good fit for” list, category, tags, link and preview image.
card.text_source — where the text comes from: seller (you wrote it), ai (AI description) or rules (automatic description from the name). Your headline and description always win over AI: AI only fills empty fields. The top-level headline and description are what you wrote (null means automatic text).
card.pronunciation — how the name is read: ipa, en (English respelling, stressed syllable in capitals), ru (Russian transcription); source: ai — from AI, rules — approximate, from reading rules (approx: true, no IPA and no stress; a respelling identical to the name itself is an empty string, and for names with digits or very long names pronunciation is null). card.meaning — the parts of the name and their origin, card.translations — the meaning of the name in en, ru, de, es, fr, it, pt, zh, ja (empty for invented names), card.language_notes — notes on how the name sounds in major languages (usually empty).
card.score — the “HQ score” 0–100: points for the name itself by HQClouds rules (length, sound, memorability, spelling, zone, meaning — taking card.meaning into account) with a hint for each part. It is not a price or an appraisal. The same fields, without labels, are also at the top level of the lot.
ai — the AI description state, always one of four statuses: ready (done, languages lists the languages), queued (we will make it in the background; with code: ai_in_progress it is already being generated by another request), disabled (it will not appear by itself, reason says why: ai_off — AI descriptions are off on the platform, auto_off — background generation is off, request it via /describe, lot_not_active — the listing is not for sale), failed (it did not work — code and reason say why). Until there is AI text, buyers see the rule-based description.
If the describe: "ai" response did not arrive (timeout, 502 or 504 from a proxy), the listing is already created: check GET /market/lots/{domain} — ai.status becomes ready when the text is done. Repeating the request is safe too: it returns the same listing, and while a generation is running there will not be a second one.
The three statuses are independent: order.status — the purchase, lot.status — the listing, ai.status — the AI description. An AI failure does not undo the listing (the lot is created — 201 with ai.status: failed) and never starts a purchase: this request moves no money.
{
"ok": false,
"error": {
"code": "order_pending",
"message": "The registrar is still processing the order — the domain can be listed once it is registered. Retry in a minute.",
"retry_after": 60
},
"order": {"id": "dq_5adc8ffc18cbcba5c488dd9e", "status": "pending"}
}
GET/market/lots/{domain}
Single listing
A listing by domain name or listing id (lot_…). If the domain was listed several times, the live listing is returned, otherwise the latest one.
{
"ok": true,
"lot": {
"id": "lot_1a2b3c4d5e6f7a8b9c0d1e2f",
"domain": "example.com",
"domain_display": "example.com",
"sld": "example",
"tld": ".com",
"length": 7,
"has_digits": false,
"has_hyphen": false,
"price": "249.00",
"currency": "USD",
"status": "for_sale",
"category": "tech",
"category_label": "Technology",
"tags": ["short", "brandable"],
"headline": null,
"description": null,
"headline_text": "A short, crisp name for a tech product",
"description_text": "Seven letters and three syllables: easy to say, spell and remember. The .com ending is what customers type first. A good fit for a SaaS product, a developer tool or an AI assistant.",
"text_auto": true,
"text_source": "ai",
"use_cases": ["SaaS startup", "developer tool", "AI assistant"],
"paid_until": "2027-10-01",
"listed_at": "2026-10-01",
"views": 0,
"raw_status": "listed",
"source": "api",
"reserved_until": null,
"sold_at": null,
"delisted_at": null,
"cancel_reason": null,
"metadata": {"score": 87, "source": "hqname"},
"url": "https://hqclouds.com/market/example.com?lang=en",
"card": {
"domain": "example.com",
"price": "249.00",
"currency": "USD",
"headline": "A short, crisp name for a tech product",
"description": "Seven letters and three syllables: easy to say, spell and remember. The .com ending is what customers type first. A good fit for a SaaS product, a developer tool or an AI assistant.",
"use_cases": ["SaaS startup", "developer tool", "AI assistant"],
"category": "tech",
"category_label": "Technology",
"tags": ["short", "brandable"],
"text_source": "ai",
"ai_languages": ["en", "ru"],
"language": "en",
"url": "https://hqclouds.com/market/example.com?lang=en",
"og_image": "https://hqclouds.com/market/example.com/og?lang=en",
"pronunciation": {"ipa": "/ɪɡˈzɑːm.pəl/", "en": "ig-ZAHM-pul", "ru": "иг-ЗАМ-пл", "latin": null, "source": "ai", "approx": false},
"meaning": [{"part": "example", "lang_ru": "английский", "lang_en": "English", "ru": "пример", "en": "example"}],
"translations": [{"lang": "ru", "text": "пример"}, {"lang": "de", "text": "Beispiel"}, {"lang": "ja", "text": "例"}],
"language_notes": [],
"score": {
"total": 95, "max": 100, "grade": "excellent", "grade_label": "excellent", "version": 2,
"parts": [
{"key": "length", "score": 20, "max": 20, "hint": "length_sweet", "label": "Length", "hint_text": "5 to 8 characters — a handy length for a brand."},
{"key": "sound", "score": 16, "max": 20, "hint": "sound_cluster", "label": "Sound", "hint_text": "A consonant cluster makes it harder to say."}
]
}
},
"ai": {"status": "ready", "languages": ["en", "ru"], "generated_at": "2026-10-01 12:00:41"}
},
"ai": {"status": "ready", "languages": ["en", "ru"], "generated_at": "2026-10-01 12:00:41"}
}
PATCH/market/lots/{domain}
Update a listing
Send only the fields you change: price, category, tags, headline, description, metadata. null or an empty string clears a field (except the price): the category and tags are detected automatically again, and the headline and description go back to the automatic description (AI or rule-based).
Scope: market:write
Parameter
Type
Required
Description
domain
path
yes
Domain name or listing id.
price
number
no
New price in USD.
category, tags
no
As when listing; null or "" means detect automatically.
headline, description
string | null
no
Your text — it always wins over AI. null or "" brings back the automatic description.
metadata
object
no
Replaces your metadata entirely; null or {} clears it. Same rules as when listing.
describe
string
no
ai — right after saving, make the AI description if there is none yet; if this same edit changed the category or tags, write it again for the new theme. To regenerate without edits, use POST …/describe with force. Can be sent without other fields.
While a buyer is paying for the domain (reserved), the listing cannot be edited, delisted or regenerated — 409 lot_reserved; once the reservation ends you can. A delisted or sold listing returns 409 lot_not_active.
The response is the whole listing with the updated card: it shows what a buyer will see now.
{
"ok": true,
"lot": {
"id": "lot_1a2b3c4d5e6f7a8b9c0d1e2f",
"domain": "example.com",
"price": "199.00",
"status": "for_sale",
"raw_status": "listed",
"headline": "Short brandable .com",
"description": null,
"url": "https://hqclouds.com/market/example.com?lang=en",
"card": {
"headline": "Short brandable .com",
"description": "Seven letters and three syllables: easy to say, spell and remember. The .com ending is what customers type first. A good fit for a SaaS product, a developer tool or an AI assistant.",
"text_source": "ai"
}
},
"ai": {"status": "ready", "languages": ["en", "ru"], "generated_at": "2026-10-01 12:00:41"}
}
POST/market/lots/{domain}/describe
AI listing description
Generates the AI description of the listing now, in the same request: the headline, description and “good fit for” list in Russian and English; it also refines the automatic category and tags. Usually 5–30 seconds, at most ~45 seconds: set a client timeout of 60 seconds. Within this request the model answers once: an unusable answer is 502 ai_failed (reason: rejected) — retry later. The platform retries by itself (with automatic descriptions on) only for a listing that has no AI text yet; a failed regeneration (force) keeps the previous AI text — retry it yourself.
Scope: market:write
Parameter
Type
Required
Description
domain
path
yes
Domain name or listing id.
force
boolean
no
true — generate again even if an AI description already exists. Without force an existing description is returned immediately (cached: true) and the generation limit is not used.
AI never overwrites your headline and description: they always win, and AI text only shows in empty fields. AI does not touch the category and tags you chose either.
Refusals: 503 ai_unavailable — AI descriptions are off; 429 ai_rate_limited — generation limit, or 2 generations of your listings are already running (reason: user_concurrency), retry after Retry-After seconds; 409 ai_in_progress — this listing is already being generated by another request; 502 ai_failed — the model gave no usable text. In every case the listing stays for sale with its previous text: the AI description if it already had one, otherwise the rule-based description.
Only for listings that are for sale: a delisted or sold listing returns 409 lot_not_active, one reserved by a buyer returns 409 lot_reserved.
If the response did not arrive (timeout), do not request it again: check GET /market/lots/{domain} — ai.status becomes ready when the text is done. A retry while the first generation is running returns 409 ai_in_progress and does not pay for a second one.
Completed sales, newest first (last 5000), plus account totals.
Scope: read
Parameter
Type
Required
Description
page
integer
no
Page number, starting from 1.
per_page
integer
no
Page size: 50 by default, 100 max.
stats: sold — domains sold, revenue — total sales, commission — marketplace commission, proceeds — credited to your balance, listed — currently for sale, views — views of active listings.
brand Brandabletech Technologyai AIbusiness Businessfinance Financecrypto Cryptoecommerce Shoppingcreative Creativesocial Communityhealth Healtheducation Educationtravel Travelfood Foodrealestate Real Estatefun Hobbies & Games
Scenario: buy and list right away
The whole path in one script — this is how the HQName integration works: find a good name, buy it from your balance, get the order number and use it to list the domain in the HQClouds marketplace with an AI description right away. Then edit and delist the same listing.
bash
export HQC_TOKEN="hqc_xxxxxxxx_..."
DOMAIN="example.com"
# Purchase key: one per purchase. The script prints it — rerunning with KEY=<the same> continues the same purchase instead of starting a new one.
KEY="${KEY:-$(uuidgen)}"
echo "Idempotency-Key: $KEY" >&2
# 1. Buy the domain. The response has the order number order.id. A network error, 5xx, busy or 429 means a retry with the same KEY: you will not be charged twice. Any other refusal (domain taken, balance, max_price) exits.
# price_changed — the price rose between the checks and nothing was charged: ask the user again. To learn the outcome without buying — GET /domains/register/$KEY.
for TRY in 1 2 3 4 5; do
RESP=$(curl -s -X POST https://hqclouds.com/api/v1/domains/register \
-H "Authorization: Bearer $HQC_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d "{\"domain\":\"$DOMAIN\",\"max_price\":15}")
ORDER=$(echo "$RESP" | jq -r '.order.id // empty' 2>/dev/null)
[ -n "$ORDER" ] && break
CODE=$(echo "$RESP" | jq -r '.error.code // empty' 2>/dev/null)
case "$CODE" in ""|busy|rate_limited|internal_error) sleep 15 ;; *) echo "$CODE" >&2; exit 1 ;; esac
done
[ -n "$ORDER" ] || { echo "no order, KEY=$KEY" >&2; exit 1; }
# 2. Wait for the outcome: GET /orders/{id} once a minute until status is registered or failed. A network error, 429 or 5xx means another attempt; any other error (token revoked, missing scope, order not found) exits.
while :; do
RESP=$(curl -s https://hqclouds.com/api/v1/orders/$ORDER \
-H "Authorization: Bearer $HQC_TOKEN")
STATUS=$(echo "$RESP" | jq -r '.order.status // empty' 2>/dev/null)
case "$STATUS" in registered|failed) break ;; esac
CODE=$(echo "$RESP" | jq -r 'if .ok == false then .error.code else empty end' 2>/dev/null)
case "$CODE" in ""|rate_limited|internal_error) sleep 60 ;; *) echo "$CODE" >&2; exit 1 ;; esac
done
[ "$STATUS" = "registered" ] || { echo "$DOMAIN: $STATUS" >&2; exit 1; }
# Platform test mode (sandbox): not a real domain — do not report a purchase to the user and do not list it.
[ "$(echo "$RESP" | jq -r '.order.registration.real')" = "true" ] || { echo "$DOMAIN: sandbox" >&2; exit 1; }
# 3. List by the order number: your price and an AI description right away. A retry returns the same listing (already_listed).
curl -s --max-time 60 -X POST https://hqclouds.com/api/v1/market/lots \
-H "Authorization: Bearer $HQC_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"order_id\":\"$ORDER\",\"price\":\"249\",\"describe\":\"ai\"}" | jq '.lot.card, .ai'
# 4. Edit: a new price and your own headline (seller text wins over AI; null brings back the automatic description).
curl -s -X PATCH https://hqclouds.com/api/v1/market/lots/$DOMAIN \
-H "Authorization: Bearer $HQC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"price":"199","headline":"Short brandable .com"}' | jq '.lot.card'
# 5. Regenerate the AI description (force — even if one already exists).
curl -s --max-time 60 -X POST https://hqclouds.com/api/v1/market/lots/$DOMAIN/describe \
-H "Authorization: Bearer $HQC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"force":true}' | jq '.ai'
# 6. Delist it if you change your mind.
curl -s -X DELETE https://hqclouds.com/api/v1/market/lots/$DOMAIN \
-H "Authorization: Bearer $HQC_TOKEN" | jq '.lot.raw_status'
The order number (dq_…) is the key to the whole path: GET /orders/{id} shows the purchase outcome, the domain and the next step, and POST /market/lots with order_id lists the domain without repeating its name. All orders — GET /orders.
Every step is safe to repeat: the purchase with the same Idempotency-Key (the script prints KEY: run it again with KEY=… and it continues the same purchase instead of starting a new one), listing returns the same listing with already_listed: true, describe without force returns the existing text, and a repeated delisting returns the already delisted listing.
A freshly bought domain is paid for a year, so the “paid for at least 30 more days” rule is met immediately.
describe: "ai" waits for the model: usually 5–30 seconds, at most ~45 seconds (the script uses --max-time 60). If AI is unavailable or hits a limit, the listing is still created — ai has status: failed with a code, and buyers see the automatic description. Without describe, the AI description appears by itself within a few minutes if it is enabled on the platform.
Buying needs a key with the domains:register scope, listing — with market:write. One key can have both.
FAQ
How much does the API cost?
The API is free. Domains bought via the API cost the same as on the website; the money is charged to your HQClouds balance.
What if a purchase request times out?
Ask how the purchase ended: GET /api/v1/domains/register/{Idempotency-Key} — it buys and charges nothing. If the order exists, the response has its status and number; “not started” (404 not_started) — repeat the POST with the same Idempotency-Key. Repeating the POST with the same key is always safe: if the purchase already went through, the API returns its result and does not charge you twice.
Can I buy a premium domain via the API?
No. A premium domain can cost hundreds of times more than a regular one, so such domains can only be bought manually in the dashboard.
How do I list a domain for sale right after buying it?
The purchase response contains the order number order.id. Wait for status: registered in GET /api/v1/orders/{id} and send POST /api/v1/market/lots with order_id — the price, category and description are optional; request an AI description with describe: "ai".
How do I revoke a token?
In the dashboard, in the “API” section: revocation takes effect immediately. Create a new key and replace the token in your software.
Ready to start?
Create a key in the dashboard — it is free and takes a minute.