ProxioDocs
API Reference

Credentials

Create and manage proxy sub-credentials on a Proxio residential service, list, create (with an optional KB/MB/GB traffic quota), update, delete, and rotate the password. Up to 20 per service.

A credential is a sub-account on a residential service: its own proxy username and password, with an optional bandwidth quota. Use separate credentials to split one package across jobs, teammates, or environments, each with its own whitelist, sessions, and usage.

Scopes: read for GET, write for everything else.

Two endpoints are residential only

Create and rotate-password check the service category and return UNSUPPORTED_OPERATION on a static category (ISP, datacenter), because those services ship a fixed proxy list rather than gateway sub-accounts. See Services.

The read and edit endpoints do not apply that check. List, get, update, and delete work on a static service's credential rows the same way they do on a residential one.

List credentials

GET /services/{id}/credentials returns the full list (up to 20), no cursor.

curl https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/credentials \
  -H "Authorization: Bearer pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
import requests

BASE = "https://dashboard.proxio.net/api/v1"
API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
SERVICE_ID = "clpkg_2a9x"

resp = requests.get(
    f"{BASE}/services/{SERVICE_ID}/credentials",
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=15,
)
resp.raise_for_status()
print(resp.json()["data"])
const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
const SERVICE_ID = "clpkg_2a9x"

const res = await fetch(`${BASE}/services/${SERVICE_ID}/credentials`, {
  headers: { Authorization: `Bearer ${API_KEY}` },
})
console.log((await res.json()).data)

200 response:

{
  "data": [
    {
      "id": "clsub_7h2k",
      "label": "Primary",
      "username": "abc123xyz",
      "password": "secretpass",
      "is_primary": true,
      "is_active": true,
      "quota": null,
      "used": { "bytes": "0", "bytes_num": 0, "gigabytes": 0 },
      "created_at": "2026-07-02T10:00:00.000Z"
    }
  ],
  "meta": { "request_id": "req_8Ke2jP4mQ" }
}

quota is null for an uncapped credential, or an object { "megabytes": <int>, "gigabytes": <number> } when a cap is set (the cap is stored as whole megabytes). Units are decimal SI: 1 GB = 1000 MB = 1,000,000 KB. used is a byte quantity.

Create a credential

POST /services/{id}/credentials. Body: label and an optional traffic cap given in one of three units. Up to 20 credentials per service.

Body

FieldTypeNotes
labelstringA name to identify the credential.
quota_mbinteger | nullTraffic cap in MB (positive integer).
quota_gbnumber | nullTraffic cap in GB (positive, decimals allowed, e.g. 1.5).
quota_kbinteger | nullTraffic cap in KB (positive integer).

Send exactly one of quota_mb, quota_gb, or quota_kb, or omit all three for an uncapped credential. The value is converted to whole megabytes (rounded up, minimum 1 MB) and capped at 10,000,000 MB. Passing an explicit null on any one of the fields clears the cap.

Providing more than one quota field (even if one is null) fails with VALIDATION_ERROR and details: [{ "field": ..., "issue": "conflicting_quota_fields" }]. A value over the maximum fails with the same code and details: [{ "field": ..., "issue": "too_large", "max_mb": 10000000 }].

curl -X POST https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/credentials \
  -H "Authorization: Bearer pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "label": "scraper-a", "quota_gb": 10 }'
import uuid
import requests

BASE = "https://dashboard.proxio.net/api/v1"
API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
SERVICE_ID = "clpkg_2a9x"

resp = requests.post(
    f"{BASE}/services/{SERVICE_ID}/credentials",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"label": "scraper-a", "quota_gb": 10},
    timeout=15,
)
resp.raise_for_status()
created = resp.json()["data"]
print(created["username"], created["password"])
import { randomUUID } from "node:crypto"

const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
const SERVICE_ID = "clpkg_2a9x"

const res = await fetch(`${BASE}/services/${SERVICE_ID}/credentials`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": randomUUID(),
  },
  body: JSON.stringify({ label: "scraper-a", quota_gb: 10 }),
})
const { data } = await res.json()
console.log(data.username, data.password)

201 response:

{
  "data": {
    "id": "clsub_9m4p",
    "label": "scraper-a",
    "username": "k7p2q1m9x3ab",
    "password": "gk2rt81wq7pz",
    "is_primary": false,
    "is_active": true,
    "quota": { "megabytes": 10000, "gigabytes": 10 },
    "used": { "bytes": "0", "bytes_num": 0, "gigabytes": 0 },
    "created_at": "2026-07-17T09:30:00.000Z"
  },
  "meta": { "request_id": "req_8Ke2jP4mQ" }
}

Reading the password later

The plaintext password is in this response, and list and get responses include it too, so any key with read scope can recover it. The one exception is an idempotent replay of this create, which returns password: null; re-read the credential instead.

Reaching the cap returns LIMIT_REACHED (409) with details: [{ "limit": 20 }].

Creating a credential also emits a credential.created webhook event, if you have an endpoint subscribed to it.

Get one credential

GET /services/{id}/credentials/{credId} returns a single credential in the same shape as a list item.

curl https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/credentials/clsub_9m4p \
  -H "Authorization: Bearer pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
import requests

BASE = "https://dashboard.proxio.net/api/v1"
API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

resp = requests.get(
    f"{BASE}/services/clpkg_2a9x/credentials/clsub_9m4p",
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=15,
)
resp.raise_for_status()
print(resp.json()["data"])
const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

const res = await fetch(
  `${BASE}/services/clpkg_2a9x/credentials/clsub_9m4p`,
  { headers: { Authorization: `Bearer ${API_KEY}` } },
)
console.log((await res.json()).data)

Update a credential

PATCH /services/{id}/credentials/{credId}. Send any of label, is_active, and a single quota field (quota_mb, quota_gb, or quota_kb). A quota value sets a new cap, and an explicit null removes it; the same one-field-only, whole-MB, and 10,000,000 MB rules as create apply. Omit the quota fields to leave the current cap unchanged.

curl -X PATCH https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/credentials/clsub_9m4p \
  -H "Authorization: Bearer pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x" \
  -H "Content-Type: application/json" \
  -d '{ "label": "scraper-a-eu", "quota_gb": null, "is_active": true }'
import requests

BASE = "https://dashboard.proxio.net/api/v1"
API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

resp = requests.patch(
    f"{BASE}/services/clpkg_2a9x/credentials/clsub_9m4p",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"label": "scraper-a-eu", "quota_gb": None, "is_active": True},
    timeout=15,
)
resp.raise_for_status()
print(resp.json()["data"])
const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

const res = await fetch(
  `${BASE}/services/clpkg_2a9x/credentials/clsub_9m4p`,
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ label: "scraper-a-eu", quota_gb: null, is_active: true }),
  },
)
console.log((await res.json()).data)

Delete a credential

DELETE /services/{id}/credentials/{credId} returns 204 No Content. The primary credential (the first one created) can't be deleted, attempting it returns UNSUPPORTED_OPERATION (400).

curl -X DELETE https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/credentials/clsub_9m4p \
  -H "Authorization: Bearer pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
import requests

BASE = "https://dashboard.proxio.net/api/v1"
API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

resp = requests.delete(
    f"{BASE}/services/clpkg_2a9x/credentials/clsub_9m4p",
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=15,
)
print(resp.status_code)  # 204
const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

const res = await fetch(
  `${BASE}/services/clpkg_2a9x/credentials/clsub_9m4p`,
  { method: "DELETE", headers: { Authorization: `Bearer ${API_KEY}` } },
)
console.log(res.status) // 204

Rotate the password

POST /services/{id}/credentials/{credId}/rotate-password generates a new password and returns it once.

curl -X POST \
  https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/credentials/clsub_9m4p/rotate-password \
  -H "Authorization: Bearer pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x" \
  -H "Idempotency-Key: $(uuidgen)"
import uuid
import requests

BASE = "https://dashboard.proxio.net/api/v1"
API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

resp = requests.post(
    f"{BASE}/services/clpkg_2a9x/credentials/clsub_9m4p/rotate-password",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    timeout=15,
)
resp.raise_for_status()
print(resp.json()["data"]["password"])  # the new password
import { randomUUID } from "node:crypto"

const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"

const res = await fetch(
  `${BASE}/services/clpkg_2a9x/credentials/clsub_9m4p/rotate-password`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Idempotency-Key": randomUUID(),
    },
  },
)
console.log((await res.json()).data.password) // the new password

200 response:

{
  "data": {
    "id": "clsub_9m4p",
    "username": "abc123xyz",
    "password": "w4hn92xcv5qm",
    "propagation_seconds": 30,
    "warning": "The previous password keeps working for up to ~30s."
  },
  "meta": { "request_id": "req_8Ke2jP4mQ" }
}

Rotation isn't instant

The new password works immediately, and the old one keeps authenticating for roughly 30 seconds (propagation_seconds). Expect a brief overlap window, and don't rely on the old password being rejected the instant you rotate.

Rotating also emits a credential.rotated webhook event. The new password is never included in it, a webhook body is a copy whose destination you don't control, so a subscriber learns that the password changed, not what it changed to.

On this page