Errors
Errors return a non-2xx status and a JSON body with a human-readable error message plus a machine-readable code where useful.
Errors return a non-2xx status and a JSON body with a human-readable error message, a machine-readable code, and the requestId of the call (the same value as the X-Request-Id response header). Scraper input errors add a details string naming the bad field. Branch on code, not on the message text: messages get reworded, codes do not. When you write to support about a failed call, include the requestId and we can find that exact request.
400Invalid product, quantity, targeting or scraper input, or a request body the endpoint does not understand (`unknown_field`, `invalid_body`).401Missing or invalid API key.402Wallet balance short, or per-key daily spend cap reached.403Key is missing the required scope, or the platform refused access to the target.404Unknown scraper, or the target does not exist (private, deleted, wrong handle).429Rate limited. Slow down and retry (Retry-After header).502Transient failure fetching from the platform. Retry shortly. Never billed.503The scraper is temporarily unavailable on our side. Retry shortly. Never billed.Request body errors
Every write body is checked against its documented fields before anything runs. A field the endpoint does not know is refused, naming the field and the accepted set, so a typo or a guessed option can never be dropped in silence and charged for.
| code | Status | Description |
|---|---|---|
unknown_field | 400 | The body carries a top-level field the endpoint does not read. The message names it and lists the accepted fields, e.g. Unknown field 'separate_plan'. This endpoint accepts: product_id, quantity, fulfillment_meta, method, expected_amount_cents. Nothing was charged. The most common case is country sent at the top level of POST /orders: a residential order carries no country at all (you choose it per line when you generate lines), and a dedicated-IP order puts it inside fulfillment_meta. See Create an order. |
invalid_body | 400 | The body is not valid JSON, or a field has the wrong type. The message includes the shape the endpoint expects. |
{
"error": "Unknown field 'separate_plan'. This endpoint accepts: product_id, quantity, fulfillment_meta, method, expected_amount_cents.",
"code": "unknown_field",
"requestId": "3f2b6d9c-7a1e-4c55-9d0e-2b8a5f6c1d47"
}Common codes
Every error on /api/v1 carries a code. These are the ones a client is most likely to branch on; endpoint-specific codes (insufficient_balance, already_applied, reseller_forbidden) are documented on their endpoints.
| code | Status | Description |
|---|---|---|
unauthorized | 401 | No key, an unknown key, or a revoked one. Send X-API-Key: hpx_…. |
account_blocked | 403 | The acting account may not transact. Contact support. |
invalid_quantity | 400 | quantity is missing, zero, negative or not a whole number. Quantities are whole GB or whole IPs; a fraction is refused rather than rounded. |
missing_product | 400 | No product_id in the body. Ids come from GET /products. |
unsupported_method | 400 | A payment method we do not offer. Orders take wallet or any rail; deposits take any rail: card, crypto, alipay, wechat, upi, momo, qris, pix, dana, fpx, oxxo, boleto, paymaya, grabpay_ph. The refusal names the full list. |
invalid_limit, invalid_offset | 400 | A paging parameter that is not a usable number. A page size above the maximum is capped, not refused. |
invalid_count, invalid_sticky | 400 | Line generation: count must be a whole number from 1 to 5000 and at most the plan's generate.maxCount, stickyMinutes a whole number of minutes, 0 or more. |
invalid_asn, invalid_os, invalid_strict, invalid_exclude, invalid_udp | 400 | Line generation on Residential Premium: asn must be a whole AS number, os one of windows / android / ios / mac, strict and udp booleans, excludeAsn / excludeCountries / excludeStates / excludeCities lists of up to 20 entries. |
not_found | 404 | The order, plan or webhook does not exist, or does not belong to the acting account. The two cases are deliberately indistinguishable. |
price_changed | 409 | The order sent an expected_amount_cents that no longer matches the price. Nothing was charged; the body carries the current amountCents, unitPriceCents and quantity, so you can show the new total and ask your buyer again. |
Gateway status codes
A generated Residential Premium line answers with its own status codes when a request cannot be served. They come from the proxy gateway, not from /api/v1, so they carry no code field; this is what each one means and what to do.
| Status | Meaning | Description |
|---|---|---|
407 | Proxy auth | The username or password on the line is wrong, or the line was generated for another plan. Regenerate the line; a whitelisted IP connects without credentials. |
403 | Bad targeting | A targeting token the gateway does not accept: a state and a city together, an ISP without a country, a misspelt place. Pick places and networks from the /geo/* lists. |
464 | Destination refused | The target host, port or protocol is blocked by the network's usage policy. |
465 | No IP for the target | Nothing free matches the targeting right now. Widen it (drop the city or ISP), or generate with strict: false to fall back within the country. |
466 | Plan traffic used up | The plan's GB are spent. Top up the plan; lines keep working as soon as the balance lands. |
467 | Session traffic cap | The session used up a traffic cap set for that one session. Lines generated here never set one; a username edited by hand with a -limit- token gets this once that cap is spent. Use a new session id. |
568 | Session error | The network's own error on one sticky session. A session can keep answering 568 after it was released, so retrying the same line does not clear it. Change the session id in the username (or generate the line again) and the next request binds a fresh IP; releasing that session once more also clears it. |
5xx | Gateway error | Retry with backoff. Persisting errors are ours to chase: tell support the time and the line's port. |
Scraper error codes
A failed scrape names its cause in code so you can handle each one differently instead of retrying everything. Nothing that fails on our side is billed — errors, timeouts and rate limits all cost nothing. The one exception is a target that does not exist: the platform was queried and answered, so that call is billed like any other, and each code below says which it is.
| code | Status | Description |
|---|---|---|
target_not_found | 404 | The profile, post or ID is not there. Private, deleted, or the handle is wrong. Retrying will not help; fix the input. Billed, because the platform was queried and gave us its answer. |
target_restricted | 403 | The platform refused it: age-gated, region-locked or login-only content. Retrying will not help. Not billed. |
bad_input | 400 | The endpoint rejected these parameters. Check the required parameters for that endpoint. Several accept one of a pair (a handle or a user id) and reject a call carrying neither. Not billed. |
provider_failed | 502 | A transient fetch failure. Safe and sensible to retry, ideally with a short backoff. Not billed. |
upstream_credits_exhausted | 503 | A capacity problem on our side, not a problem with your request. Retry shortly; if it persists, contact us. Not billed. |
upstream_auth_rejected | 503 | As above: ours to fix, not yours. Retry shortly and tell us if it continues. Not billed. |
{
"error": "No data found for this target. It may be private, deleted, or the handle/URL may be wrong.",
"code": "target_not_found"
}502 with the same generic message, so a mistyped handle looked identical to an outage. The statuses and codes above are what the API returns now.Need a hand wiring this up? Email support@hproxy.com. A real person reads every message.