Proxy Descriptors API

Generate a ready-to-use proxy descriptor — a fully assembled connection string with host, port, username, and password — for one of your active sub-users. This is the recommended way to hand a downstream customer a working proxy: you pick the sub-user, protocol, geo-targeting, session behaviour, and AI filter, and the API returns a single url your customer can drop straight into any client. Responses are wrapped in the standard {success, payload, errors, description} envelope.

You never build the username by hand. The descriptor endpoint bakes the geo, session, and filter tokens into the username for you (e.g. -country-, -region-, -city-, -sid-, -ttl-, -filter-medium). If you would rather assemble the username yourself, see the raw connection grammar — but for handing a finished proxy to a customer, this endpoint is the safer path.

Generate Proxy Descriptor

POST /v1/proxy-descriptors Requires Auth

Generate Proxy Descriptor

Generate Proxy Descriptor

Build a ready-to-use proxy descriptor for one of your active sub-users. The sub-user must belong to your account and be in the "active" lifecycle state, otherwise the endpoint returns 404. Geo-targeting, session stickiness, and the AI filter are all optional; the returned username has every requested token pre-assembled.

We filled these in for you: sub_user_uuid auto protocol location.country session.mode filter

Tweak any value if you like — or just press Try.

Request Body
Name Type Required Description
sub_user_uuid uuid Required UUID of the sub-user to generate the descriptor for. Must be a sub-user you own whose lifecycle_status is "active".
protocol string Required Proxy protocol. One of "http" or "socks5". Determines the returned port (8080 for http, 1080 for socks5).
location object Optional Optional geo-targeting object. Omit for a random location (the username uses country "any"). Omit any individual key you do not want to constrain — the string "any" is a no-selection sentinel for country ONLY; for region/city/isp it is taken as a literal value. A zip / zipcode / postal key is prohibited and refuses the request with 422. See "Two things that will surprise you" below.
location.country string Optional Country code to target, e.g. "us". Max 32 chars. Lower-cased into the -country- token; defaults to "any" when omitted.
location.region string Optional Region/state CODE to target, e.g. "california" — not the display name. Max 128 chars. Adds a -region- token.
location.city string Optional City CODE to target, e.g. "los_angeles" — not the display name. Max 128 chars. Requires location.region to also be set — a city given without a region cannot be resolved upstream and is refused with 422, rather than silently widened to the whole region or country. Adds a -city- token.
location.isp string Optional ISP/carrier CODE to target, e.g. "comcast_cable" — not the display name. Max 128 chars. Adds an -isp- token.
connection_type string Optional Connection type to target. One of "residential" (the default — adds no token) or "mobile" (adds a -type-mobile token).
ipv4_only boolean Optional Restrict to IPv4-only exit nodes. Defaults to false. When true, adds an -ipv4-true token.
session object Optional Optional session-control object. Omit for a rotating (per-request) IP.
session.mode string Optional Session mode: "rotating" (default — new IP each request) or "sticky" (hold one IP). Only "sticky" adds -sid-/-ttl- tokens.
session.ttl string Optional Legacy sticky-session lifetime. One of "30m" or "12h". Defaults to "30m". Only applied when mode is "sticky". Mutually exclusive with session.ttl_seconds — supplying both is a 422.
session.ttl_seconds integer Optional Sticky-session lifetime in seconds, 60-86400. Converted into the wire format's humanized string (e.g. 600 -> "10m"), which cannot always round-trip an arbitrary value exactly — see ttl_effective_seconds below. Mutually exclusive with session.ttl — supplying both is a 422.
session.id string Optional Sticky-session identifier: 8-32 hexadecimal characters. If omitted while mode is "sticky", a random 16-hex id is generated for you.
filter string Optional AI filter tier. One of "filter-high", "filter-high-speed-fast", "filter-medium", "filter-medium-speed-fast", or "none". Defaults to "filter-medium". "none" leaves the filter token off entirely.
curl -X POST https://api.proxyhat.com/v1/proxy-descriptors \
  -H "Authorization: Bearer __API_KEY__" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "sub_user_uuid": "7c9f2a1b-4d3e-4f5a-9b6c-1e2d3f4a5b6c",
    "protocol": "http",
    "location": { "country": "US", "region": "California", "city": "Los Angeles" },
    "session": { "mode": "sticky", "ttl": "30m" },
    "filter": "filter-medium"
  }'
import requests

response = requests.post(
    "https://api.proxyhat.com/v1/proxy-descriptors",
    headers={
        "Authorization": "Bearer __API_KEY__",
        "Content-Type": "application/json",
        "Accept": "application/json",
    },
    json={
        "sub_user_uuid": "7c9f2a1b-4d3e-4f5a-9b6c-1e2d3f4a5b6c",
        "protocol": "http",
        "location": {"country": "US", "region": "California", "city": "Los Angeles"},
        "session": {"mode": "sticky", "ttl": "30m"},
        "filter": "filter-medium",
    },
)

descriptor = response.json()["payload"]
print(descriptor["url"])
const response = await fetch("https://api.proxyhat.com/v1/proxy-descriptors", {
  method: "POST",
  headers: {
    "Authorization": "Bearer __API_KEY__",
    "Content-Type": "application/json",
    "Accept": "application/json",
  },
  body: JSON.stringify({
    sub_user_uuid: "7c9f2a1b-4d3e-4f5a-9b6c-1e2d3f4a5b6c",
    protocol: "http",
    location: { country: "US", region: "California", city: "Los Angeles" },
    session: { mode: "sticky", ttl: "30m" },
    filter: "filter-medium",
  }),
});

const { payload } = await response.json();
console.log(payload.url);
payload := map[string]interface{}{
    "sub_user_uuid": "7c9f2a1b-4d3e-4f5a-9b6c-1e2d3f4a5b6c",
    "protocol":      "http",
    "location":      map[string]string{"country": "US", "region": "California", "city": "Los Angeles"},
    "session":       map[string]string{"mode": "sticky", "ttl": "30m"},
    "filter":        "filter-medium",
}
body, _ := json.Marshal(payload)

req, _ := http.NewRequest("POST", "https://api.proxyhat.com/v1/proxy-descriptors", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer __API_KEY__")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

var result struct {
    Payload struct {
        URL string `json:"url"`
    } `json:"payload"`
}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result.Payload.URL)

Two things that will surprise you

ZIP targeting is not a field — sending one is a 422, not a no-op. location.zip, location.zipcode and location.postal are prohibited inputs: supplying any of them refuses the whole request. This catches people out because ProxyHat's own product surfaces a ZIP dropdown, so a location object modelled on it carries a zipcode key. The upstream gateway answers 407 to a -zip- token and silently ignores the -zipcode-/-postal- aliases, so there is no ZIP targeting to deliver — and refusing tells you that, where quietly dropping the field would leave you believing it worked. Strip the key before you post. Scope by country / region / city / ISP instead.
The string "any" means "unset" for country only. Omitting location.country — or sending "any" — produces the literal token -country-any, which is what "anywhere" looks like on the wire. region, city and isp do not work that way: they have no sentinel, so {"region": "any"} is treated as a region literally named any and emits -region-any — a token the dashboard never produces and the gateway has no reason to recognise. To leave region / city / ISP unconstrained, omit the key entirely (or send null) — never the string "any". If your UI uses "any" as its no-selection value (ProxyHat's own dashboard does), filter those values out before building the request body.
Validation failures use the standard Laravel shape, not the envelope. A 422 returns { "message": "...", "errors": { "<field>": ["..."] } } — including the city-without-region refusal, which is keyed to location.city. The {success, payload, errors, description} envelope is used for success and for the 404, so one parser handles every validation error this endpoint can return.

Response Fields

On success the payload carries everything a downstream client needs. Hand the customer either the ready-made url, or the individual host / port / username / password parts.

payload
FieldTypeDescription
provider_idstringAlways "proxyhat".
sub_user_uuiduuidThe sub-user this descriptor was generated for.
protocolstringEchoes the requested protocol (http or socks5).
filterstringThe applied AI filter (defaults to filter-medium when not specified).
hoststringGateway hostname — always gate.proxyhat.com.
portinteger8080 for http, 1080 for socks5.
usernamestringThe sub-user's proxy username with all geo, session, and filter tokens baked in.
passwordstringThe sub-user's proxy password.
urlstringFully assembled connection URL: protocol://username:password@host:port.
host_socks5stringConvenience host:port string for SOCKS5 (gate.proxyhat.com:1080), regardless of the requested protocol.
host_httpstringConvenience host:port string for HTTP (gate.proxyhat.com:8080), regardless of the requested protocol.
ttl_effective_secondsinteger|nullThe sticky-session window actually granted, in seconds — derived from the -ttl- token that was emitted into the username, not echoed from your request. Because the wire format is a humanized string, an arbitrary session.ttl_seconds value cannot always round-trip exactly: e.g. a request for 12345 emits 3h25m and reports 12300, 45 seconds short. null when session.mode is not "sticky".
Sub-user must be active and yours. If sub_user_uuid does not match an active sub-user owned by the authenticated account, the endpoint returns 404 in the standard envelope: { "success": false, "payload": null, "errors": ["Sub-user not found"], "description": "Sub-user not found" }. Suspended, deleting, or deleted sub-users are treated the same as non-existent.

How the username is assembled

The username starts from the sub-user's base proxy username and appends tokens for each option you set:

Send location codes, not display names. Values are passed through to the gateway exactly as you supply them, apart from being lower-cased — so use the code field from the locations endpoints (los_angeles, comcast_cable), not the human label ("Los Angeles", "Comcast Cable"). Codes legitimately contain characters like ., (, ) and / (for example the real ISP codes cbvision_s.a. and telenor_a/s), and these reach the gateway intact.

Two characters are refused with a 422 rather than rewritten: a hyphen, because - separates the username's tokens and a value containing one would inject an extra token into your credential; and whitespace, which no real location code contains and which almost always means a display name was sent instead of a code.

The returned username and password are the literal credential — use those. The url field is a convenience rendering and percent-encodes its user:pass@ section, so a code containing :, @ or / cannot re-cut the host and port; decode it with any standard URL parser. For the full grammar and how to build these usernames yourself, see Connecting to the Proxy.