Errors
Error format, status codes and error codes of the Domain API.
Format
Every error has the same envelope:
{ "success": false, "error": "Domain not found", "code": "NOT_FOUND" }erroris a message for humans. Some messages are in Indonesian (for exampleSaldo tidak cukuporLengkapi data pemilik domain dulu: ...), and wording can change.codeis stable. Build your error handling on it.- Some errors add fields, for example
missingonREGISTRANT_INCOMPLETE.
Status codes
| Status | Meaning | Charged? |
|---|---|---|
400 | The request was understood but refused: unsupported extension or period, premium name, owner details incomplete. | No |
401 | Missing, invalid or expired API key. | No |
402 | Wallet balance too low for a paid call. | No |
403 | The key is not allowed to use the Domain API. | No |
404 | Unknown route, the domain does not exist on your account, or an unknown or non-numeric host or price id. | No |
409 | The name is taken or already in your account, the domain is waiting for owner details, a renewal is already running, or WHOIS protection is already on. | No |
422 | The body or query does not match the schema (VALIDATION_ERROR), or the registry rejected the request (REGISTRAR_REJECTED). | Refunded |
429 | Rate limit exceeded. Wait for retry-after seconds. | No |
500 | An unexpected error. | Refunded |
502 | The registry could not be reached. | Refunded |
For paid calls, the charge happens only after every check that returns 400, 402, 404 or 409 passes. When the registry then rejects the request (422 REGISTRAR_REJECTED) or cannot be reached (502 REGISTRAR_ERROR), the amount is refunded to your wallet automatically.
Error codes
code | Status | Meaning and what to do |
|---|---|---|
NO_API_KEY | 401 | Send Authorization: Bearer hel_your_key. |
INVALID_API_KEY | 401 | The key is unknown or revoked, or the account is inactive. Create a new key. |
API_KEY_EXPIRED | 401 | The key passed its expiry date. Create a new key. |
SCOPE_NOT_ALLOWED | 403 | The key does not have the Domain API product. Create a key with Domain API enabled. |
ACCOUNT_KEY_REQUIRED | 403 | The key is limited to one project. Domains belong to the account: create a key with Whole account access. |
TLD_NOT_SUPPORTED | 400 | The extension cannot be registered or transferred through Helipod, or has no price. See Supported extensions. |
PERIOD_NOT_SUPPORTED | 400 | period is not 1. Transfers and renewals are for one year. |
PRICE_UNAVAILABLE | 400 | No valid price exists for this extension right now. Try again later or contact support. |
PREMIUM_NOT_SUPPORTED | 400 | Premium names cannot be registered online. Contact support for a quote. |
REGISTRANT_INCOMPLETE | 400 | Owner details are missing. missing lists the fields. Complete them with PATCH /domains/registrant or in the dashboard. See Owner details. |
INVALID_REGISTRANT | 400 | An owner detail sent to PATCH /domains/registrant was refused. error says which. |
DOMAIN_NOT_LINKED | 400 | The domain is not active at the registry yet, so it cannot be renewed. |
INSUFFICIENT_BALANCE | 402 | The wallet cannot cover the price. Top up and retry. |
DOMAIN_NOT_FOUND | 404 | The domain id does not exist, is not a number, belongs to another account, or the domain is not registered at the registry yet. |
NOT_FOUND | 404 | The route does not exist, a host or price id is unknown or not a number, or the registry does not know the requested item. |
DOMAIN_UNAVAILABLE | 409 | The name belongs to another account. Nothing was charged. |
DOMAIN_ALREADY_IN_ACCOUNT | 409 | The name is already in your account. Nothing was charged. |
DOMAIN_PENDING_PROFILE | 409 | The domain is a free domain waiting for owner details. Activate it in the dashboard. See Pending profile domains. |
RENEW_IN_PROGRESS | 409 | The domain is already being renewed (by another call or by auto-renew). Read it again in a moment. |
WHOIS_PROTECTION_ACTIVE | 409 | WHOIS protection is already on. Nothing was charged. |
VALIDATION_ERROR | 422 | A required field is missing or has the wrong type or value. |
REGISTRAR_REJECTED | 422 | The registry rejected the request: name not available, wrong auth code, invalid record, unknown host. error gives the reason when it can be shown. Fix the input; the charge, if any, was refunded. |
RATE_LIMITED | 429 | More than 10 requests per second with this key. Back off and retry. |
INTERNAL_ERROR | 500 | Unexpected error. Retry later; contact support if it persists. |
REGISTRAR_ERROR | 502 | The registry could not be reached. The message is generic. Retry later; the charge, if any, was refunded. |
REQUEST_ERROR | 4xx | A refused request whose detail cannot be shown. |
Another account's domain and a malformed id both return 404, never 403, so the API does not reveal which domain ids exist.
Delivered but not yet recorded
In rare cases the registry completes a registration, transfer or renewal but Helipod cannot record it right away. The call still succeeds, because the domain was delivered and paid for, and nothing is refunded:
{
"success": true,
"code": "DOMAIN_SAVE_PENDING",
"message": "example.com is done, but it does not show in your dashboard yet. Please contact support and we will add it for you.",
"data": { "name": "example.com", "status": 1, "status_label": "Active" },
"billing": { "registration": 199000, "whoisProtection": 0, "total": 199000, "depositCharged": 199000 }
}A register or transfer answers 201, a renewal 200. When code is DOMAIN_SAVE_PENDING, contact support and do not retry the call: a retry would charge you again.
Example: handling errors
async function domainApi(path, init = {}) {
const res = await fetch(`https://api.helipod.io/api${path}`, {
...init,
headers: {
Authorization: "Bearer hel_your_key",
"Content-Type": "application/json",
...init.headers,
},
});
const body = await res.json();
if (body.success) {
if (body.code === "DOMAIN_SAVE_PENDING") console.warn(body.message); // delivered: contact support, do not retry
return body;
}
if (res.status === 429) {
const wait = Number(res.headers.get("retry-after") ?? 1);
throw new Error(`Rate limited, retry in ${wait}s`);
}
if (body.code === "REGISTRANT_INCOMPLETE") {
throw new Error(`Complete owner details first: ${body.missing.join(", ")}`);
}
if (body.code === "INSUFFICIENT_BALANCE") throw new Error("Top up your wallet");
throw new Error(`${res.status} ${body.code ?? "ERROR"}: ${body.error}`);
}