Reseller API
Programmatic access for approved resellers: browse your product catalog, place balance-paid orders, retrieve proxy credentials, create a separate sub-account for each of your customers, and monitor traffic usage — all over a simple REST API.
API at a glance
- Base URL:
https://hellworld.io/openapi/v1(https://hellworld.io/hellworld/openapi/v1is an equivalent path to the same API) - Auth:
Authorization: Bearer <key_id>.<key_secret> - Format: JSON over HTTPS
- Billing: prepaid account balance (USD)
- Rate limit: per reseller — 300 requests/min (reads), 60/min (writes: orders and sub-account create / top-up / reclaim). Up to 5 active keys per reseller.
Overview
The Reseller API is available to approved reseller accounts. Your account manager enables specific products for your account and configures your reseller pricing. Only products enabled for your account appear in the API.
The typical integration loop:
GET /products— see your enabled products and unit pricesPOST /orders— place an order, paid from your prepaid balanceGET /orders/{order_ref}— confirm order stateGET /proxies— retrieve proxy credentials and traffic usage- Build proxy strings using the per-product gateway formats below
Reselling to end customers who each need their own login? Use Sub-accounts instead: POST /subusers creates a separate credential with its own traffic quota. You can top it up individually, and reclaim its unused traffic in whole GB for a refund (see DELETE) — reclaiming does not disconnect the sub-account.
Currently available products:
| product_code | Product | Category | Billing | Sub-accounts |
|---|---|---|---|---|
MOBILE-ET | ET Mobile | mobile | per GB | yes |
RES-GEOFAST | Geofast | residential | per GB | — |
RES-ETH | ET Hearth | residential | per GB | yes |
A product is usable only once it is enabled and priced for your account — it then appears in your GET /products. More products can be enabled on request.
Authentication
Create an API key in your Reseller Portal (Dashboard → Reseller Portal → API Keys). A key has two parts:
- Key ID — starts with
dk_live_, safe to log - Key Secret — shown only once at creation; store it securely
Send both on every request, joined by a dot:
Authorization: Bearer dk_live_AbCdEf1234567890.YOUR_KEY_SECRETcurl -s https://hellworld.io/openapi/v1/products \
-H "Authorization: Bearer dk_live_AbCdEf1234567890.YOUR_KEY_SECRET"Common pitfalls
- The header value is
Bearer+ one space +key_id.key_secret— the first dot separates ID from secret. - Write requests also require
Content-Type: application/jsonand anIdempotency-Keyheader (see Idempotency). - Rate limits are per reseller (shared across all your keys): 300 requests/min for reads, 60/min for writes (orders plus sub-account create / top-up / reclaim, one shared budget). On
429 RATE_LIMITED, honor theRetry-Afterresponse header. You may hold up to 5 active API keys. - Keys can be revoked and re-created in the portal at any time. Revocation is immediate.
- Python's built-in
urllibsends a defaultUser-Agentthat our edge network blocks (403). Set your ownUser-Agentheader; the defaults ofrequests,curl, Node and Go work as-is.
Authentication errors:
| HTTP | code | Meaning |
|---|---|---|
| 401 | AUTH_MISSING_KEY | No Authorization header |
| 401 | AUTH_INVALID_KEY | Malformed key, unknown key ID, or secret mismatch |
| 401 | AUTH_KEY_REVOKED | Key was revoked in the portal |
| 401 | AUTH_KEY_EXPIRED | Key past its expiry date |
| 403 | DEALER_SUSPENDED | Reseller account disabled — contact your account manager |
| 429 | RATE_LIMITED | Over the per-reseller rate limit (300/min read, 60/min write); see Retry-After |
API errors share one envelope:
{ "error": { "code": "INSUFFICIENT_BALANCE", "message": "Account balance is insufficient for this order" } }A request the server can't parse at all — a write with no body or invalid JSON, a missing Content-Type: application/json, a field of the wrong JSON type (for example a numeric product_code), or a non-numeric pageNum / pageSize — gets a generic error response without this envelope instead (it can be a 4xx or a 500). Nothing is created or charged in that case; fix the request and send it again.
Products
GET /products
Returns the products enabled for your account, with your unit prices.
curl -s https://hellworld.io/openapi/v1/products \
-H "Authorization: Bearer $KEY"{
"data": [
{
"product_code": "MOBILE-ET",
"display_name": "ET Mobile",
"category": "mobile",
"billing_unit": "gb",
"unit_price": 2.50,
"currency": "USD"
},
{
"product_code": "RES-GEOFAST",
"display_name": "Geofast",
"category": "residential",
"billing_unit": "gb",
"unit_price": 0.30,
"currency": "USD"
}
]
}unit_price is your negotiated reseller price per billing_unit. A product without a unit_price has no price set for your account yet and can't be bought (orders and sub-accounts return 422 NO_DEALER_PRICE) — ask your account manager to configure it.
Balance
GET /balance
curl -s https://hellworld.io/openapi/v1/balance \
-H "Authorization: Bearer $KEY"{ "balance": 152.40, "currency": "USD" }Topping up: log into your account on hellworld.io and use the normal balance recharge (PayPal / crypto / other supported channels), or arrange an offline payment with your account manager — it is credited to the same balance the API spends from.
Orders
POST /orders
Places an order paid from your prepaid balance. Requires an Idempotency-Key header — 1–64 ASCII characters; allowed characters are A–Z, a–z, 0–9, ., _, : and - (spaces are not allowed; a UUID fits). Anything else is rejected with 422 IDEMPOTENCY_KEY_INVALID.
curl -s -X POST https://hellworld.io/openapi/v1/orders \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6679-7425-40de-963d-1a4a6e4c1b2f" \
-d '{ "product_code": "RES-GEOFAST", "quantity": 10 }'| Field | Type | Required | Meaning |
|---|---|---|---|
product_code | string | yes | From GET /products |
quantity | number | yes | Amount in billing_unit (GB), must be > 0 |
Success — 200:
{
"order_ref": "a1b2c3d4e5f6...",
"state": "active",
"total_amount": 3.00,
"currency": "USD"
}Traffic is added to your account's proxy credential for that product (see Proxies & Usage). Repeat purchases of the same product top up the same credential.
Accepted — 202 (manual review):
{
"order_ref": "a1b2c3d4e5f6...",
"state": "manual_review",
"message": "Order was recorded but pending manual verification; do not retry this request"
}202 means you were already charged — do NOT retry
A 202 manual_review response means the balance was deducted and the order was recorded, but automatic fulfillment needs manual confirmation. Retrying with a new Idempotency-Key would place (and charge) a second order. Poll GET /orders/{order_ref} until the state becomes completed.
Errors:
| HTTP | code | Meaning |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Missing Idempotency-Key header |
| 422 | IDEMPOTENCY_KEY_INVALID | Key not 1–64 chars of A-Za-z0-9._:- |
| 402 | INSUFFICIENT_BALANCE | Top up your balance first |
| 404 | PRODUCT_NOT_FOUND | No such product available for your account (unknown code, or not enabled for you) |
| 422 | NO_DEALER_PRICE | No price configured for you — contact your account manager |
| 409 / 422 | IDEMPOTENCY_KEY_CONFLICT | See Idempotency |
| 422 | VALIDATION_ERROR | Missing/invalid product_code or quantity |
| 422 | ORDER_REJECTED | Order could not be created |
| 500 | ORDER_STATE_UNKNOWN | Outcome unverified — do NOT retry with a new key; poll GET /orders or contact support |
| 403 | SANDBOX_KEY_DISABLED | Sandbox keys are not usable yet — create a live key |
Idempotency
Every POST /orders must carry an Idempotency-Key. Semantics:
| Situation | Result |
|---|---|
| Same key, same body, finished before | The original response is replayed (no duplicate charge) |
| Same key, same body, still processing | 409 IDEMPOTENCY_KEY_CONFLICT — wait, then repeat the same request to fetch the stored result |
| Same key, different body | 422 IDEMPOTENCY_KEY_CONFLICT — a key can only ever be used for one request body |
Request failed with a validation-type error (VALIDATION_ERROR, PRODUCT_NOT_FOUND, INSUFFICIENT_BALANCE, …) | The error is replayed for that key; these are proven side-effect-free — retry with a new key |
Request ended in ORDER_STATE_UNKNOWN | Outcome could not be verified. Never retry with a new key (possible double charge) — poll GET /orders, or contact support quoting the key |
Generate a fresh UUID per order attempt and persist it with your local order record. Keys must match [A-Za-z0-9._:-]{1,64} — max 64 characters, no spaces.
GET /orders
Paginated order history (newest first).
curl -s "https://hellworld.io/openapi/v1/orders?pageNum=1&pageSize=20" \
-H "Authorization: Bearer $KEY"{
"data": [
{
"order_ref": "a1b2c3d4e5f6...",
"order_no": "CZ20260825...",
"state": "completed",
"total_amount": 3.00,
"currency": "USD",
"created_at": "2026-08-25 10:12:03",
"products": [
{ "product_code": "RES-GEOFAST", "display_name": "Geofast", "quantity": 10 }
]
}
],
"pagination": { "page": 1, "page_size": 20, "total": 42 }
}pageSize is capped at 100.
GET /orders/{order_ref}GET /orders/{ref}
Single order lookup — use this to poll the outcome of a 202 manual_review order.
curl -s https://hellworld.io/openapi/v1/orders/a1b2c3d4e5f6 \
-H "Authorization: Bearer $KEY"Returns the same object shape as the list endpoint. 404 ORDER_NOT_FOUND if the ref does not exist or does not belong to your account.
Order states:
| state | Meaning |
|---|---|
pending | Created, not yet paid |
completed | Paid; fulfillment done (traffic credited) |
manual_review | Paid; awaiting manual fulfillment confirmation — poll until completed |
failed | Payment failed; nothing charged |
disputed | Under payment dispute |
refunded | Refunded |
unknown | Any other internal state |
Proxies & Usage
GET /proxies
Your proxy credentials with traffic usage — one entry per enabled product you have purchased.
curl -s https://hellworld.io/openapi/v1/proxies \
-H "Authorization: Bearer $KEY"{
"data": [
{
"product_code": "RES-GEOFAST",
"display_name": "Geofast",
"billing_unit": "gb",
"username": "hw_ab12cd34",
"password": "s3cr3tPass",
"traffic": { "limit_gb": 50.0, "used_gb": 12.345, "remaining_gb": 37.655 },
"status": "active",
"expires_at": null
}
],
"usage_refresh_seconds": 60
}| Field | Meaning |
|---|---|
username / password | Credentials for the product's proxy gateway (formats below) |
traffic.limit_gb | Total GB ever purchased (topped up cumulatively) |
traffic.used_gb | GB consumed, as last recorded by us — may be out of date (see below) |
traffic.remaining_gb | limit - used, floored at 0 |
status | active, or suspended (e.g. traffic exhausted) |
expires_at | null for never-expiring GB balances |
usage_refresh_seconds | The shortest interval between usage refresh attempts — not a promise of how recent the values are |
Usage may be out of date
used_gb and remaining_gb are the values we last recorded. At the moment, usage may not be refreshed when you call GET /proxies, so these numbers can be well behind actual consumption — by much more than 60 seconds. Treat them as approximate and don't use them to bill your customers or to decide when to cut them off. Polling more than once a minute won't give you fresher data.
Buying more of the same product (POST /orders) increases limit_gb on the same credential — you do not get a new username per order.
Sub-accounts
A sub-account is a separate proxy credential — its own username / password and its own traffic quota — that you create through the API for one of your customers. You top it up individually and can reclaim its unused traffic in whole GB for a refund, without touching your other customers. Reclaiming does not disconnect a sub-account: it stays active with the same credentials (see DELETE).
Orders (POST /orders) | Sub-accounts (/subusers) | |
|---|---|---|
| Credential | One shared credential per product (GET /proxies) | A new credential for every sub-account |
| Traffic | Adds GB to your shared credential | Each sub-account has its own quota |
| Unused traffic | Stays on your shared credential | Reclaim it in whole GB with DELETE /subusers/{subuser_ref} and get a refund; the sub-account stays active |
| Ending one customer's access | Not possible without affecting the others | No API call for it — DELETE doesn't disable a sub-account or change its credentials; see Ending a customer's access |
Listed in GET /orders, webhooks | Yes | No — they show only in your balance |
| Products | Any product enabled for you | MOBILE-ET, RES-ETH |
Use orders when you consume the traffic yourself or share one login; use sub-accounts when you resell to end customers who each need their own login and quota.
Endpoints (same base URL, key and rate limits as the rest of this API):
| Method | Path | Does | Charges |
|---|---|---|---|
POST | /subusers | Create a sub-account | unit_price × quota_gb |
GET | /subusers | List your sub-accounts | — |
GET | /subusers/{subuser_ref} | Credentials, quota and usage | — |
PATCH | /subusers/{subuser_ref} | Add traffic | unit_price × add_gb |
DELETE | /subusers/{subuser_ref} | Reclaim unused traffic in whole GB (the sub-account stays active) | Refunds reclaimed_gb × the lowest price you paid |
Products & billing
Sub-accounts are available for these products (click a product for its connection format):
You can use one only when it appears in your GET /products with a unit_price; otherwise creation is refused with 404 PRODUCT_NOT_FOUND (not enabled for you) or 422 NO_DEALER_PRICE (no price set) and nothing is charged. GET /products does not mark which products support sub-accounts — use the table above.
How charging works
- Create and top-up are paid from your prepaid balance at your
unit_priceper GB:amount = unit_price × GB, rounded to the cent (5 GB at $2.50 → $12.50). The price is fixed at the moment of the request. - The GB amount must be at least
0.001(it is rounded to 6 decimals) and the charge at least $0.01; smaller requests are refused with422 VALIDATION_ERROR. - Reclaiming refunds whole GB.
DELETEtakes back the whole-GB part of a sub-account's unused paid traffic and refunds it to your balance at the lowest per-GB price you paid for that sub-account; what stays on the sub-account (for example a fraction below 1 GB) is not refunded. See DELETE. - Unknown outcomes keep the charge. If a create or top-up ends with
500 SUBUSER_STATE_UNKNOWNor500 TOPUP_STATE_UNKNOWN, any amount already taken stays deducted while the outcome is reviewed (a few cases settle automatically within minutes; the rest are checked manually by us). Repeat the same request to see the latest state, don't retry with a new key (see Idempotency & retries), and contact support. Likewise, a reclaim that ends with500 REVOKE_STATE_UNKNOWNrefunds nothing while its outcome is reviewed. - Sub-account charges and refunds are not orders: they don't appear in
GET /ordersand no webhook is sent.GET /balancereflects them immediately — keep your own record persubuser_ref.
Effect of each result on your balance:
| Result | Balance |
|---|---|
200 create / top-up | Charged unit_price × GB |
200 RECLAIMED (DELETE) | Refunded refunded_amount |
Create / top-up refused: 400, 402, 404, 503; 422 VALIDATION_ERROR / NO_DEALER_PRICE / LIMIT_EXCEEDED / IDEMPOTENCY_KEY_*; 409 SUBUSER_REF_CONFLICT / SUBUSER_OP_IN_FLIGHT / SUBUSER_NOT_ACTIVE, or IDEMPOTENCY_KEY_CONFLICT for a key used on another endpoint | Not charged |
422 SUBUSER_CREATE_FAILED, 422 TOPUP_FAILED | Charged and refunded — net zero |
409 "still processing" (SUBUSER_IN_FLIGHT, TOPUP_IN_FLIGHT, REVOKE_IN_FLIGHT, IDEMPOTENCY_KEY_CONFLICT) | The original request is still running — a create or top-up may already be charged, a reclaim may still refund. Repeat the same request with the same key later to get its result; never switch to a new key |
500 SUBUSER_STATE_UNKNOWN, 500 TOPUP_STATE_UNKNOWN | Charge kept pending review |
500 REVOKE_STATE_UNKNOWN (DELETE) | No refund while the outcome is reviewed |
200 NOTHING_TO_RECLAIM, or a DELETE refused with 400, 404, 422, 501, or 409 REVOKE_NOT_STARTED / SUBUSER_OP_IN_FLIGHT | Nothing charged, nothing refunded |
| Same request repeated with the same key | Never charged or refunded twice |
Quick start
Create → fetch credentials → connect → top up → reclaim. Every write needs its own Idempotency-Key (a fresh UUID); $KEY is your key_id.key_secret.
1. Create a 5 GB ET Mobile sub-account. subuser_ref is your ID for the customer:
curl -s -X POST https://hellworld.io/openapi/v1/subusers \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0b6f3c2e-8d1a-4f7b-9c55-2a7e4d1f6b90" \
-d '{ "product_code": "MOBILE-ET", "subuser_ref": "cust-1001", "quota_gb": 5 }'{
"subuser_ref": "cust-1001",
"status": "active",
"username": "USERNAME",
"password": "PASSWORD",
"traffic": { "limit_gb": 5.000000 }
}2. Fetch the credentials again whenever you need them:
curl -s https://hellworld.io/openapi/v1/subusers/cust-1001 \
-H "Authorization: Bearer $KEY"3. Connect — give your customer a proxy string in the product's format:
curl -x etmobile.hellworld.io:5000 \
-U "USERNAME-country-us:PASSWORD" \
https://ipinfo.io4. Top up 2 GB (wait a few minutes after the previous write — see Timing):
curl -s -X PATCH https://hellworld.io/openapi/v1/subusers/cust-1001 \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5d2a9e71-3c4b-4e08-a6f1-9b0c7e2d4a13" \
-d '{ "add_gb": 2 }'{ "subuser_ref": "cust-1001", "status": "active", "traffic": { "limit_gb": 7.000000 } }5. Reclaim the unused traffic when the customer leaves (again a few minutes after the previous write, while the sub-account is idle):
curl -s -X DELETE https://hellworld.io/openapi/v1/subusers/cust-1001 \
-H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: 9a4e1b7c-6f2d-4a83-b5c0-1d8e3f7a2c64"{
"subuser_ref": "cust-1001",
"status": "active",
"code": "RECLAIMED",
"reclaimed_gb": 6.000000,
"refunded_amount": 15.00,
"currency": "USD",
"remaining_gb": 0.996000,
"message": "…"
}Here the customer had used 0.004 GB of the 7 GB, so 6 whole GB came back and $15.00 (6 × $2.50) was added to your balance. The sub-account is still active with the same credentials, and the 0.996 GB left on it keeps working for your customer — DELETE does not disconnect a sub-account (see Ending a customer's access). Rules and other results: DELETE.
POST /subusersCreate (POST)
Creates a sub-account and charges unit_price × quota_gb from your balance. Requires Idempotency-Key.
| Field | Type | Required | Meaning |
|---|---|---|---|
product_code | string | yes | MOBILE-ET or RES-ETH, as a JSON string (must be enabled and priced for you) |
subuser_ref | string | yes | Your own ID for this sub-account: 1–128 characters of A-Za-z0-9._:-, case-sensitive. Unique for your account forever — it can never be reused for another sub-account |
quota_gb | number | yes | Traffic quota in GB, > 0 (rounded to 6 decimals; at least 0.001 GB and $0.01) |
Success — 200:
{
"subuser_ref": "cust-1001",
"status": "active",
"username": "USERNAME",
"password": "PASSWORD",
"traffic": { "limit_gb": 5.000000 }
}username/passwordare returned here, when you repeat this exact request with the same key, and byGET /subusers/{subuser_ref}while the sub-account isactive. Store them securely.- The response has no
product_code,created_ator usage — useGET /subusers/{subuser_ref}for those. - Creation takes about 10 seconds. Use a client timeout of at least 60 seconds; if it times out, repeat the same request with the same key — never a new key or a new
subuser_ref.
Errors:
| HTTP | code | Meaning / what to do |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Missing Idempotency-Key header |
| 422 | IDEMPOTENCY_KEY_INVALID | Key not 1–64 chars of A-Za-z0-9._:- |
| 422 | VALIDATION_ERROR | Missing field, quota_gb not a positive number, bad subuser_ref format, or amount below 0.001 GB / $0.01 |
| 404 | PRODUCT_NOT_FOUND | Product doesn't support sub-accounts or isn't enabled for your account |
| 422 | NO_DEALER_PRICE | No price configured for you — contact your account manager |
| 402 | INSUFFICIENT_BALANCE | Top up, then retry with a new key (the same subuser_ref is still free) |
| 409 | SUBUSER_REF_CONFLICT | This subuser_ref was already used by an earlier request with a different key — pick a new subuser_ref |
| 409 / 422 | IDEMPOTENCY_KEY_CONFLICT | See Idempotency & retries |
| 409 | SUBUSER_IN_FLIGHT | You repeated a request that is still being processed — repeat it later with the same key; if it's unchanged after about 20 minutes, contact support |
| 409 | SUBUSER_SUSPENDED | You repeated a create whose sub-account was suspended by the earlier DELETE behaviour (see DELETE) |
| 422 | SUBUSER_CREATE_FAILED | Definitely not created; the charge was refunded. Retry with a new subuser_ref and a new key |
| 503 | SUBUSER_UNAVAILABLE | Creation temporarily unavailable; nothing charged. Retry later with a new key |
| 500 | SUBUSER_STATE_UNKNOWN | Outcome could not be verified; the charge (if taken) is kept pending review. Do not use a new key or a new subuser_ref; repeat the same request to see the latest state, or contact support with the key and subuser_ref. Also returned when you repeat a create whose sub-account is being reclaimed or awaits support after a reclaim — that sub-account was created and charged once, and repeating doesn't charge again |
GET /subusersList (GET)
Your sub-accounts, newest first. Query parameters: pageNum (default 1) and pageSize (default 20, max 100).
curl -s "https://hellworld.io/openapi/v1/subusers?pageNum=1&pageSize=20" \
-H "Authorization: Bearer $KEY"{
"data": [
{
"subuser_ref": "cust-1001",
"product_code": "MOBILE-ET",
"status": "active",
"traffic": { "limit_gb": 7.000000 },
"created_at": "2026-10-08T02:14:50"
}
],
"pagination": { "page": 1, "page_size": 20, "total": 1 }
}- Lists sub-accounts that are
active,pendingorunknown(see Status values); a sub-account stays listed after a reclaim. Sub-accounts suspended by the earlierDELETEbehaviour, and ones that definitely failed to create, are not listed. - List items carry no credentials and no usage — fetch a single sub-account for those.
created_atis UTC, written asYYYY-MM-DDTHH:MM:SSwithout an offset.
GET /subusers/{subuser_ref}Details (GET)
One sub-account with its credentials, quota and usage.
curl -s https://hellworld.io/openapi/v1/subusers/cust-1001 \
-H "Authorization: Bearer $KEY"{
"subuser_ref": "cust-1001",
"product_code": "MOBILE-ET",
"status": "active",
"username": "USERNAME",
"password": "PASSWORD",
"traffic": {
"limit_gb": 7.000000,
"used_gb": 0.000000,
"remaining_gb": 7.000000,
"usage_source": "cached"
},
"usage_refresh_seconds": 60,
"created_at": "2026-10-08T02:14:50"
}| Field | Meaning |
|---|---|
username / password | Proxy credentials — returned only while status is active |
traffic.limit_gb | Total GB bought for this sub-account (create + top-ups), minus any GB taken back with DELETE |
traffic.used_gb | GB consumed, as last recorded by us |
traffic.remaining_gb | limit_gb − used_gb, floored at 0 |
traffic.usage_source | live = refreshed during this request; cached = last recorded value, possibly out of date |
usage_refresh_seconds | A refresh is attempted at most once per this many seconds per sub-account |
created_at | Creation time, UTC, no offset |
How fresh the usage numbers are
used_gb / remaining_gb are refreshed from the network during the request, at most once per usage_refresh_seconds per sub-account. usage_source: "live" means this response carries a fresh reading; "cached" means the last recorded value (for example when a refresh was skipped or failed) and may be behind actual consumption. Use only live values to bill or cut off your customers.
Errors: 404 SUBUSER_NOT_FOUND — no such sub-account on your account (also if it failed to create, or was suspended by the earlier DELETE behaviour); 500 SUBUSER_CRED_UNREADABLE — credentials temporarily unreadable, contact support with the subuser_ref.
PATCH /subusers/{subuser_ref}Top up (PATCH)
Adds traffic to an active sub-account and charges unit_price × add_gb. The username and password stay the same. Requires Idempotency-Key.
curl -s -X PATCH https://hellworld.io/openapi/v1/subusers/cust-1001 \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5d2a9e71-3c4b-4e08-a6f1-9b0c7e2d4a13" \
-d '{ "add_gb": 2 }'| Field | Type | Required | Meaning |
|---|---|---|---|
add_gb | number | yes | GB to add, > 0 (rounded to 6 decimals; at least 0.001 GB and $0.01) |
Success — 200 — limit_gb is the new total quota:
{ "subuser_ref": "cust-1001", "status": "active", "traffic": { "limit_gb": 7.000000 } }Errors:
| HTTP | code | Meaning / what to do |
|---|---|---|
| 400 / 422 | IDEMPOTENCY_KEY_REQUIRED / IDEMPOTENCY_KEY_INVALID | Missing or malformed key |
| 422 | VALIDATION_ERROR | add_gb missing or not positive, bad subuser_ref format, or amount below 0.001 GB / $0.01 |
| 404 | SUBUSER_NOT_FOUND | No active sub-account with this ref (doesn't exist, suspended by the earlier DELETE behaviour, or currently pending / unknown) |
| 404 | PRODUCT_NOT_FOUND | The product is no longer enabled for your account |
| 422 | NO_DEALER_PRICE | No price configured for you |
| 402 | INSUFFICIENT_BALANCE | Top up, then retry with a new key |
| 409 | SUBUSER_OP_IN_FLIGHT | Another top-up or reclaim on this sub-account hasn't finished. Nothing charged. Once it has finished, retry with a new key — repeating this key only returns this same 409 |
| 409 | SUBUSER_NOT_ACTIVE | The sub-account stopped being active while the request ran; nothing charged |
| 422 | LIMIT_EXCEEDED | The new total would exceed the maximum quota; nothing charged |
| 422 | TOPUP_FAILED | Not applied; the charge was refunded. Usually the sub-account was changed moments ago — wait 2–3 minutes and retry with a new key. If it keeps happening, contact support |
| 409 | TOPUP_IN_FLIGHT | You repeated a request that is still being processed (it may already be charged) — repeat it later with the same key, never a new one; if it's unchanged after about 20 minutes, contact support |
| 409 / 422 | IDEMPOTENCY_KEY_CONFLICT | See Idempotency & retries |
| 500 | TOPUP_STATE_UNKNOWN | Outcome could not be verified; the charge is kept pending review. Until it is resolved, further top-ups and reclaims on this sub-account return 409 SUBUSER_OP_IN_FLIGHT (its credentials keep working). Do not use a new key; contact support with the key and subuser_ref |
DELETE /subusers/{subuser_ref}Reclaim (DELETE)
DELETE does not disconnect the sub-account
DELETE takes back unused traffic in whole GB and refunds it to your balance. It does not disable the sub-account: the sub-account stays active, its username and password don't change, and the traffic left on it keeps working for your customer. There is currently no API call that disables a sub-account or changes its password — see Ending a customer's access.
This replaces the earlier behaviour, in which DELETE suspended the sub-account and refunded nothing.
Reclaims a sub-account's unused traffic in whole GB and refunds it to your balance. No request body. Requires Idempotency-Key.
curl -s -X DELETE https://hellworld.io/openapi/v1/subusers/cust-1001 \
-H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: 9a4e1b7c-6f2d-4a83-b5c0-1d8e3f7a2c64"Success — 200. The top-level code tells you whether anything was reclaimed. For the sub-account from the quick start (7 GB bought at $2.50 per GB, 0.004 GB used):
{
"subuser_ref": "cust-1001",
"status": "active",
"code": "RECLAIMED",
"reclaimed_gb": 6.000000,
"refunded_amount": 15.00,
"currency": "USD",
"remaining_gb": 0.996000,
"message": "…"
}Called again a few minutes later (with a new key), less than 1 GB is left to reclaim:
{
"subuser_ref": "cust-1001",
"status": "active",
"code": "NOTHING_TO_RECLAIM",
"reclaimed_gb": 0.000000,
"refunded_amount": 0.00,
"currency": "USD",
"remaining_gb": 0.996000,
"message": "…"
}| Field | Meaning |
|---|---|
code | RECLAIMED — whole GB were taken back (reclaimed_gb) and refunded_amount was added to your balance. NOTHING_TO_RECLAIM — less than 1 GB of unused paid traffic is left: nothing was taken back or refunded, and nothing changed |
status | active — the sub-account is not disabled and keeps the same credentials (it may have 0 GB left) |
reclaimed_gb | Whole GB taken back (0 for NOTHING_TO_RECLAIM) |
refunded_amount / currency | Amount added to your balance, in USD (0.00 for NOTHING_TO_RECLAIM) |
remaining_gb | Traffic actually left on the sub-account after this call (including any bonus traffic) — your customer can keep using it. It can differ from traffic.remaining_gb in GET /subusers/{subuser_ref}, which counts only the traffic you paid for |
message | Human-readable summary; it may change — branch on code |
How much is reclaimed and refunded
- Reclaimed: the whole-GB part of the sub-account's unused paid traffic —
limit_gb − used_gb, rounded down to a whole number of GB. Usage is measured at the moment of the request, not taken from the cachedused_gbofGET. It is never more than the traffic actually left on the sub-account. - Stays with the sub-account: everything else — the fraction below 1 GB, and any traffic beyond what you paid for. It is not reclaimed or refunded; settle it with your customer yourself.
- Refund: reclaimed GB × the lowest per-GB price you paid for this sub-account (at creation or at any successful top-up), rounded to the cent the same way charges are. The refunds for a sub-account never add up to more than you were charged for it.
- After a reclaim,
limit_gbinGET /subusers/{subuser_ref}is lower byreclaimed_gb. - If less than 1 GB of unused paid traffic is left — including a sub-account that has used up all of its traffic — the result is
NOTHING_TO_RECLAIM: nothing is taken back or refunded and nothing changes, so it's safe to call again.
Example: a sub-account created with 8 GB at $2.50 per GB and later topped up with 2 GB at $2.00 per GB — 10 GB bought, $24.00 charged, refunds at $2.00 per GB:
| Used when you reclaim | code | reclaimed_gb | refunded_amount | Left on the sub-account |
|---|---|---|---|---|
| 5.2 GB | RECLAIMED | 4 | 8.00 | 0.8 GB |
| 0 GB | RECLAIMED | 10 | 20.00 | 0 GB — it stays active with no traffic |
| 9.6 GB | NOTHING_TO_RECLAIM | 0 | 0.00 | 0.4 GB |
| 10 GB (all used) | NOTHING_TO_RECLAIM | 0 | 0.00 | 0 GB |
When to reclaim
- Reclaim while the sub-account is idle. If your customer is still sending traffic through it while the reclaim runs, the result may not be confirmable: you get
500 REVOKE_STATE_UNKNOWNand nothing is refunded while it is reviewed (see If a reclaim can't be confirmed). - Leave about 3 minutes after a top-up or a previous reclaim on the same sub-account. A reclaim sent too early is refused safely with
409 REVOKE_NOT_STARTED(nothing changed) — retry later with a new key. - A reclaim takes about 10 seconds. Meanwhile the sub-account shows
status: "unknown",GET /subusers/{subuser_ref}returns no credentials, and it can't be topped up or reclaimed again. The sub-account is not disabled and its credentials don't change; whether your customer can still use it depends on how much traffic is actually left.
Sub-accounts suspended earlier. Before this change, DELETE suspended the sub-account. Those sub-accounts keep their earlier result: repeating that request returns the same 200 as before — code: "QUOTA_SUSPENDED" (revocation: "drain_confirmed") or code: "QUOTA_SUSPENDED_UNVERIFIED" (revocation: "unverified") — and a new DELETE on such a sub-account returns 200 with code: "QUOTA_SUSPENDED_UNVERIFIED". Both come with status: "suspended" and carry no refund; such a sub-account is no longer listed or returned by GET. No other sub-account gets these two codes.
Errors (the codes of this endpoint keep their earlier REVOKE_… names):
| HTTP | code | Meaning / what to do |
|---|---|---|
| 400 / 422 | IDEMPOTENCY_KEY_REQUIRED / IDEMPOTENCY_KEY_INVALID | Missing or malformed key |
| 422 | VALIDATION_ERROR | Bad subuser_ref format |
| 404 | SUBUSER_NOT_FOUND | No such sub-account on your account |
| 409 | REVOKE_NOT_STARTED | The reclaim didn't start — usually because the sub-account was changed moments ago (a top-up or a previous reclaim). Nothing changed and nothing was refunded. Wait 2–3 minutes and retry with a new key; if it keeps happening, contact support |
| 409 | SUBUSER_OP_IN_FLIGHT | Another top-up or reclaim on this sub-account is in progress, or it can't be reclaimed in its current state (pending / unknown). Nothing changed. Once it clears, retry with a new key — repeating this key only returns this same 409; if it persists, contact support |
| 409 | REVOKE_IN_FLIGHT | You repeated a reclaim that is still running — repeat it later with the same key; if it's unchanged after about 20 minutes, contact support |
| 409 | REVOKE_IN_PROGRESS_OR_FAILED | This key has no result yet. Repeat the same request shortly; if it keeps returning this, use a new key or contact support |
| 409 / 422 | IDEMPOTENCY_KEY_CONFLICT | Key already used on another endpoint, or for another sub-account |
| 501 | REVOKE_NOT_SUPPORTED | Reclaim isn't available for this sub-account's product. Nothing changed |
| 500 | REVOKE_STATE_UNKNOWN | The reclaim's result couldn't be confirmed. No refund while it is reviewed — see If a reclaim can't be confirmed. Do not use a new key; contact support with the key and subuser_ref |
If a reclaim can't be confirmedUnconfirmed reclaim
500 REVOKE_STATE_UNKNOWN means the reclaim was sent but its result couldn't be confirmed — for example because traffic was flowing through the sub-account at that moment, or the result on the network or our settlement couldn't be confirmed — or it couldn't be completed automatically:
{ "error": { "code": "REVOKE_STATE_UNKNOWN", "message": "…" } }What that means:
- No refund for now — nothing is added to your balance while we review it.
- The sub-account is not disabled and its credentials don't change, but some or all of its traffic may already have been taken back — whether your customer can still use it depends on what is actually left. Until the result is confirmed, don't assume nothing was taken back.
- Until support has resolved it, the sub-account shows
status: "unknown"inGET /subusersandGET /subusers/{subuser_ref}. The detail returns no credentials, and the API can't top it up (404 SUBUSER_NOT_FOUND) or reclaim it again (409 SUBUSER_OP_IN_FLIGHTwith a new key). - Repeating the same request with the same
Idempotency-Keyis safe but returns the same result — it doesn't resolve on its own. - Repeating the sub-account's original create request meanwhile returns
500 SUBUSER_STATE_UNKNOWN. That is not a new charge: the sub-account was created and charged once.
What to do: contact support with the subuser_ref and the Idempotency-Key you used. Don't retry with a new key.
Ending a customer's accessEnding access
There is currently no API call that disables a sub-account or changes its password, and DELETE doesn't do either. When a customer leaves:
- Stop topping the sub-account up.
- Once it is idle, call
DELETEto reclaim its unused whole GB for a refund. - What stays on the sub-account — the fraction below 1 GB, and any traffic beyond what you paid for — keeps working with the same credentials until it is used up. Settle that part with your customer yourself.
A subuser_ref can't be reused for another sub-account; give a new customer a new ref.
Idempotency & retriesIdempotent retries
POST /subusers, PATCH and DELETE all require an Idempotency-Key — 1–64 characters of A-Za-z0-9._:-, same as orders. Use a fresh UUID for every new operation and store it with your record, so you can repeat the request after a timeout.
| Situation | Result |
|---|---|
| Same key, same request, already finished | Returns that operation's current result; never charged or refunded twice. While the sub-account is active — also after a reclaim — a repeated create returns the current credentials and the current limit_gb (after later top-ups and reclaims); while a reclaim is running or awaits support it returns 500 SUBUSER_STATE_UNKNOWN, and for a sub-account suspended by the earlier DELETE behaviour 409 SUBUSER_SUSPENDED — none of these is a new charge. A repeated reclaim returns the same code and amounts as the first time |
Same key, same request, still running: 409 SUBUSER_IN_FLIGHT, TOPUP_IN_FLIGHT, REVOKE_IN_FLIGHT, REVOKE_IN_PROGRESS_OR_FAILED, or IDEMPOTENCY_KEY_CONFLICT ("already being processed") | Repeat the same request with the same key later. Don't switch to a new key — a create or top-up may already be charged and would run a second time, and a reclaim may still complete. These normally clear within seconds; if the same 409 keeps coming back for more than about 20 minutes, contact support with the key and subuser_ref |
Same key, different body or different subuser_ref | 422 IDEMPOTENCY_KEY_CONFLICT |
Same key already used on another endpoint (including POST /orders) | 409 IDEMPOTENCY_KEY_CONFLICT — keys are shared by all your write endpoints |
Refused with 400, 402, 404, 422, 503, or a 409 other than the "still running" codes above (for example SUBUSER_REF_CONFLICT, SUBUSER_OP_IN_FLIGHT, REVOKE_NOT_STARTED) | Fix the cause and retry with a new key (after SUBUSER_CREATE_FAILED or SUBUSER_REF_CONFLICT, also a new subuser_ref); the old key may keep returning the same error |
500 SUBUSER_STATE_UNKNOWN / TOPUP_STATE_UNKNOWN / REVOKE_STATE_UNKNOWN | Never retry with a new key or a new subuser_ref — that could create or charge twice. Repeating the same request is safe and returns the latest state; contact support with the key and subuser_ref |
- A repeat must be exactly the same request: same path, same JSON fields in the same order, same number formatting (
1and1.0differ). Whitespace doesn't matter. - Treat keys as case-insensitive: never use two keys that differ only in letter case.
- Keys don't expire.
- Handle results by
code;messagetexts are for humans and may change.
Status values
| status | Meaning | Credentials in detail | Top up / reclaim |
|---|---|---|---|
active | Ready to use — also after a reclaim | yes | yes |
pending | Being created (normally about 10 seconds) | no | no |
unknown | A reclaim is running, or an outcome is being verified / awaits support | no | no |
suspended | Only sub-accounts suspended by the earlier DELETE behaviour: appears only in the DELETE response, next to code QUOTA_SUSPENDED / QUOTA_SUSPENDED_UNVERIFIED; such a sub-account is not returned otherwise | — | — |
Timing & good practiceTiming & tips
- Writes take about 10 seconds. Use a client timeout of at least 60 seconds; after a timeout, repeat the same request with the same key.
- Leave about 3 minutes between two writes on the same sub-account (create → top-up → reclaim). Readings take a little while to settle after a change; a write sent too early is refused safely —
422 TOPUP_FAILED(refunded) or409 REVOKE_NOT_STARTED(nothing changed) — and you retry later with a new key. DELETEnever cuts a customer off: the sub-account staysactivewith the same credentials. See Ending a customer's access.- Reclaim only while the sub-account is idle; with traffic flowing, the result may not be confirmable (
500 REVOKE_STATE_UNKNOWN, no refund while it is reviewed). - Writes share the 60 requests/min reseller write limit with
POST /orders; reads have 300/min. - Sub-account credentials work with your custom gateway domain exactly like
GET /proxiescredentials.
Proxy Formats
Build proxy strings with the username / password from GET /proxies, or from a sub-account (GET /subusers/{subuser_ref}) — the syntax is the same. Country targeting and sticky sessions are encoded in the username.
ET Mobile (MOBILE-ET)ET Mobile
| Gateway | etmobile.hellworld.io |
| Port | 5000 |
| Protocols | HTTP / HTTPS |
Rotating (new IP per request):
etmobile.hellworld.io:5000:USERNAME-country-us:PASSWORDSticky session (same IP for the TTL):
etmobile.hellworld.io:5000:USERNAME-country-us-sid-a1b2c3d4-ttl-10m:PASSWORD-country-<cc>— optional 2-letter country code, lowercase (omit for global random)-sid-<id>— any 8-char alphanumeric session ID; samesid= same IP-ttl-<N>m— session lifetime in minutes
curl -x etmobile.hellworld.io:5000 \
-U "USERNAME-country-us-sid-a1b2c3d4-ttl-10m:PASSWORD" \
https://ipinfo.ioGeofast (RES-GEOFAST)Geofast
| Gateway | geofast.hellworld.io |
| Port (HTTP/HTTPS) | 6969 |
| Port (SOCKS5) | 9696 |
Rotating:
geofast.hellworld.io:6969:USERNAME-country-US:PASSWORDSticky session (~10 min, same session = same IP):
geofast.hellworld.io:6969:USERNAME-country-US-session-a1b2c3d4e5:PASSWORD-country-<CC>— optional 2-letter country code, uppercase (omit for global random)-session-<id>— any 10-char alphanumeric session ID
curl -x geofast.hellworld.io:6969 \
-U "USERNAME-country-US-session-a1b2c3d4e5:PASSWORD" \
https://ipinfo.ioFor SOCKS5 use port 9696 with the same username syntax.
ET Hearth (RES-ETH)ET Hearth
| Gateway | ethearth.hellworld.io |
| Port | 5000 |
| Protocols | HTTP / HTTPS |
Rotating (new IP per request):
ethearth.hellworld.io:5000:USERNAME-country-us:PASSWORDSticky session (same IP for the TTL):
ethearth.hellworld.io:5000:USERNAME-country-us-sid-a1b2c3d4-ttl-10m:PASSWORD-country-<cc>— optional 2-letter country code, lowercase (omit for global random)-sid-<id>— any 8-char alphanumeric session ID; samesid= same IP-ttl-<N>m— session lifetime in whole minutes, 1–240- State, city and ISP targeting are also available (they go between the country and the session parts); ask your account manager for the codes.
curl -x ethearth.hellworld.io:5000 \
-U "USERNAME-country-us-sid-a1b2c3d4-ttl-10m:PASSWORD" \
https://ipinfo.ioCustom Gateway Domain (White-label)Custom Gateway Domain
By default your customers connect to the shared gateway hostnames above (etmobile.hellworld.io, geofast.hellworld.io, ethearth.hellworld.io). If you want them to connect through your own branded domain, add a CNAME record pointing at our gateway — no code changes, and no impact on speed.
Setup
Add one CNAME per gateway you use, pointing at our gateway hostname:
| Your record | Type | Target |
|---|---|---|
mproxy.yourbrand.com | CNAME | etmobile.hellworld.io |
proxy.yourbrand.com | CNAME | geofast.hellworld.io |
rproxy.yourbrand.com | CNAME | ethearth.hellworld.io |
Customers then connect exactly as before, swapping only the hostname — ports and username syntax are unchanged:
proxy.yourbrand.com:6969:USERNAME-country-US-session-a1b2c3d4e5:PASSWORDRules
- Always CNAME to our
*.hellworld.iogateway hostname (e.g.geofast.hellworld.io) — never to a raw IP address. We may update the gateway's underlying IPs at any time; because your record points at our hostname, those changes are picked up automatically with no action on your side. A record pinned to a fixed IP will break when the IP changes. - DNS-only — do NOT put a CDN/proxy layer in front (e.g. Cloudflare's orange-cloud). Proxy ports are raw TCP; routing them through a CDN breaks the connection. Keep the record grey-cloud / DNS-only.
- Ports stay the same (ET Mobile
5000, ET Hearth5000, Geofast HTTP6969/ SOCKS59696). - Credentials are still issued by this API — a custom domain only rebrands the hostname; the
username/passwordalways come fromGET /proxiesor from a sub-account.
Does it affect speed?
No. A custom domain is a pure DNS alias — proxy traffic still goes directly to the gateway IP and never passes through our servers or the extra hostname. The added CNAME hop only affects the first DNS lookup (a few milliseconds, then cached); actual proxy latency and throughput are identical to using the shared hostname.
Webhooks
Get notified when an API order is created, instead of polling. Configure the target URL in the Reseller Portal (Webhook section) — you receive a signing secret there. Sub-account operations don't send webhooks.
Event: order.created — sent after an API order is recorded (active or manual_review):
{
"event_type": "order.created",
"event_id": "…",
"dealer_uuid": "…",
"created_at": "2026-08-25T10:12:03Z",
"data": {
"order_ref": "a1b2c3d4e5f6...",
"state": "active",
"total_amount": 3.00,
"currency": "USD"
}
}Signature — every delivery carries:
X-Dealer-Signature: sha256=<hex>where <hex> is HMAC-SHA256(webhook_secret, raw_request_body). Verify before trusting:
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}Delivery & retries: your endpoint must return 2xx within a few seconds. Failed deliveries are retried with exponential backoff (30s, 60s, 120s, … capped at 1h) up to 12 attempts, then dropped. Use the portal's Test webhook button to verify your endpoint (webhook.test event).
TIP
Webhooks are best-effort push. For a guaranteed view of order outcomes (especially manual_review transitions), poll GET /orders/{order_ref}.
Error Reference
| HTTP | code | Where | Meaning |
|---|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | all writes | Missing Idempotency-Key header |
| 422 | IDEMPOTENCY_KEY_INVALID | all writes | Key not 1–64 chars of A-Za-z0-9._:- |
| 401 | AUTH_MISSING_KEY | all | No Authorization header |
| 401 | AUTH_INVALID_KEY | all | Malformed/unknown key or bad secret |
| 401 | AUTH_KEY_REVOKED | all | Key revoked |
| 401 | AUTH_KEY_EXPIRED | all | Key expired |
| 402 | INSUFFICIENT_BALANCE | POST /orders, POST/PATCH /subusers | Prepaid balance too low |
| 403 | DEALER_SUSPENDED | all | Reseller account disabled |
| 404 | PRODUCT_NOT_FOUND | POST /orders, POST/PATCH /subusers | No such product available for your account |
| 422 | NO_DEALER_PRICE | POST /orders, POST/PATCH /subusers | No price set for your account — contact your account manager |
| 404 | ORDER_NOT_FOUND | GET /orders/{ref} | No such order on your account |
| 409 | IDEMPOTENCY_KEY_CONFLICT | all writes | Same key still processing — repeat later; or key already used on another endpoint |
| 422 | IDEMPOTENCY_KEY_CONFLICT | all writes | Same key, different body (or different subuser_ref) |
| 422 | VALIDATION_ERROR | all writes | Bad or missing field, or amount too small |
| 422 | ORDER_REJECTED | POST /orders | Order refused |
| 429 | RATE_LIMITED | all | Over per-reseller limit (300/min read, 60/min write); see Retry-After |
| 500 | INTERNAL_ERROR | any | Unexpected server error |
| 500 | ORDER_STATE_UNKNOWN | POST /orders | Outcome unverified — never retry with a new key; poll GET /orders |
| 403 | SANDBOX_KEY_DISABLED | all | Sandbox keys are disabled until the sandbox launches |
| 404 | SUBUSER_NOT_FOUND | /subusers/{ref} | No such sub-account on your account (for PATCH: no active one) |
| 409 | SUBUSER_REF_CONFLICT | POST /subusers | subuser_ref already used — pick a new one |
| 409 | SUBUSER_IN_FLIGHT | POST /subusers | Still being created — repeat the same request (same key) later |
| 409 | SUBUSER_SUSPENDED | POST /subusers | Repeated create of a sub-account suspended by the earlier DELETE behaviour |
| 422 | SUBUSER_CREATE_FAILED | POST /subusers | Not created, charge refunded — retry with a new subuser_ref and key |
| 503 | SUBUSER_UNAVAILABLE | POST /subusers | Creation temporarily unavailable, not charged — retry later with a new key |
| 500 | SUBUSER_STATE_UNKNOWN | POST /subusers | Outcome unverified, charge kept pending review (or, on a repeated create, the sub-account is being reclaimed or awaits support after a reclaim) — never retry with a new key or subuser_ref |
| 500 | SUBUSER_CRED_UNREADABLE | GET /subusers/{ref} | Credentials temporarily unreadable — contact support |
| 409 | SUBUSER_OP_IN_FLIGHT | PATCH, DELETE /subusers/{ref} | Another operation is running, or the sub-account can't be reclaimed now — retry later with a new key |
| 409 | SUBUSER_NOT_ACTIVE | PATCH /subusers/{ref} | Sub-account is no longer active; not charged |
| 422 | LIMIT_EXCEEDED | PATCH /subusers/{ref} | New total would exceed the maximum quota; not charged |
| 422 | TOPUP_FAILED | PATCH /subusers/{ref} | Not applied, charge refunded — retry in 2–3 min with a new key |
| 409 | TOPUP_IN_FLIGHT | PATCH /subusers/{ref} | Still processing, may already be charged — repeat the same request (same key) later |
| 500 | TOPUP_STATE_UNKNOWN | PATCH /subusers/{ref} | Outcome unverified, charge kept pending review — never retry with a new key |
| 409 | REVOKE_NOT_STARTED | DELETE /subusers/{ref} | Reclaim didn't start, nothing changed — retry in 2–3 min with a new key |
| 409 | REVOKE_IN_FLIGHT | DELETE /subusers/{ref} | Still processing — repeat the same request (same key) later |
| 409 | REVOKE_IN_PROGRESS_OR_FAILED | DELETE /subusers/{ref} | No result yet for this key — repeat shortly; then a new key or support |
| 501 | REVOKE_NOT_SUPPORTED | DELETE /subusers/{ref} | Reclaim isn't available for this product; nothing changed |
| 500 | REVOKE_STATE_UNKNOWN | DELETE /subusers/{ref} | Result unverified, no refund while it is reviewed — never retry with a new key; contact support (see If a reclaim can't be confirmed) |
FAQ
How do I become a reseller / get products enabled?
Reseller accounts are set up by our team: your account is registered as a reseller, products are whitelisted, and your pricing is configured. Contact support or your account manager.
How do I pay?
The API spends your prepaid account balance. Top up online (log into hellworld.io → balance recharge: PayPal, crypto, and other supported channels) or arrange an offline payment with your account manager.
I placed an order but GET /proxies shows nothing.
GET /proxies only lists products that are (a) enabled for your account and (b) already purchased at least once. Right after your first order it can take a moment for the credential to be provisioned — check GET /orders/{order_ref} is completed, then retry.
Does every order create a new proxy account?
No. Purchases of the same product accumulate on one credential per product — limit_gb grows, the username stays the same. If you need a separate login per customer, use Sub-accounts.
How fresh is used_gb?
Not guaranteed. At the moment, usage may not be refreshed when you call GET /proxies, so used_gb can be well behind actual consumption. usage_refresh_seconds (60) is only the shortest interval between refresh attempts, not a promise of freshness. Treat the value as approximate, don't bill or cut off customers based on it, and don't poll more than once a minute — that won't give you fresher data. For sub-accounts, see the next question.
Is sub-account usage live?
GET /subusers/{subuser_ref} refreshes usage during the request, at most once per usage_refresh_seconds per sub-account. Check usage_source: live means this response carries a fresh reading; cached is the last recorded value (the refresh was skipped or failed) and may be behind actual consumption. The refresh interval is a throttle, not a freshness guarantee. Bill or cut off customers only on live values.
Sub-account or order — which should I use?
Orders top up one shared credential per product — fine when you use the traffic yourself. Sub-accounts give each customer their own login and quota, which you can top up separately and reclaim separately (unused whole GB, refunded to your balance). See Sub-accounts.
Can I reuse a subuser_ref?
Not once a sub-account has been created with it. After a create that returned 200, 409 SUBUSER_IN_FLIGHT, 422 SUBUSER_CREATE_FAILED or 500 SUBUSER_STATE_UNKNOWN, treat the ref as taken forever — also after a reclaim. A create that was refused before anything was created (400, 402, 404, 503, or 422 VALIDATION_ERROR / NO_DEALER_PRICE / IDEMPOTENCY_KEY_*) leaves the ref free. Use a new ref for every new sub-account (for example your customer ID plus a counter).
Is unused sub-account traffic refunded?
In whole GB, yes. DELETE /subusers/{subuser_ref} reclaims the whole-GB part of a sub-account's unused paid traffic and refunds it at the lowest per-GB price you paid for that sub-account. The fraction below 1 GB, and any traffic beyond what you paid for, stays on the sub-account and isn't refunded. Details: DELETE.
Where do I see what sub-accounts cost me?
In your balance (GET /balance). Sub-account charges and refunds are not orders, so they don't appear in GET /orders and don't send webhooks — keep your own log per subuser_ref and Idempotency-Key.
Does DELETE cut my customer off?
No. DELETE reclaims unused whole GB for a refund; the sub-account stays active, its credentials don't change, and the traffic left on it keeps working. There is currently no API call that disables a sub-account or changes its password — see Ending a customer's access.
My reclaim returned 500 REVOKE_STATE_UNKNOWN. What now?
The reclaim's result couldn't be confirmed — for example because traffic was flowing through the sub-account at that moment, or the result on the network or our settlement couldn't be confirmed. Nothing is refunded while we review it, and until then the API can't top up or reclaim that sub-account. It is not disabled and its credentials don't change, but its remaining traffic may already have been reduced or taken back. Contact support with the subuser_ref and the Idempotency-Key. Don't retry with a new key. Details: If a reclaim can't be confirmed.
My top-up returned 422 TOPUP_FAILED (or my reclaim 409 REVOKE_NOT_STARTED).
Nothing happened: no traffic was added or taken back, and nothing was charged or refunded (a top-up charge is refunded right away). This is usually because the sub-account was changed moments before — wait 2–3 minutes and retry with a new Idempotency-Key. If it keeps happening, contact support.
I got 202 manual_review — was I charged?
Yes. The order is recorded and your balance was deducted; fulfillment just needs a manual confirmation on our side. Do not retry with a new Idempotency-Key (that would be a second, separate order). Poll GET /orders/{order_ref}.
Is there a sandbox?
Not yet — sandbox keys are currently rejected on all API calls (403 SANDBOX_KEY_DISABLED). A mock sandbox is planned.
Can I get more products over the API?
Yes — any catalogued product can be enabled for your account. Ask your account manager.