Usage
Read bandwidth usage for a Proxio service. GET /services/{id}/usage returns a summary (limit, used, remaining, today, success rate) and GET /services/{id}/usage/series returns a zero-filled time series with close-reason breakdowns. Explains the byte-quantity object.
Two endpoints report bandwidth for a service: a summary for headline numbers and a series for charts. Both express byte amounts as quantity objects, so start with how those work.
Scope: read
Byte quantities
Bandwidth is metered in bytes, and exact byte counts can exceed what a JSON number holds precisely. To avoid precision loss, every byte-valued field is a quantity object with three views of the same value:
"remaining": {
"bytes": "10485760000",
"bytes_num": 10485760000,
"gigabytes": 10.4858
}| Field | Type | Notes |
|---|---|---|
bytes | string | Exact integer bytes, as a string. Always present, always authoritative. |
bytes_num | number | null | The same value as a JSON number, present only when it fits safely (up to 9007199254740991); otherwise null. |
gigabytes | number | Convenience float (bytes / 1e9), rounded to 4 decimals. |
Prefer the bytes string
Read bytes (the string) when you need exactness. Use bytes_num only for
quick math where you've confirmed it isn't null, and treat gigabytes as a
display convenience. For realistic package sizes bytes_num is virtually
always populated, but the string is the field to trust.
For unlimited packages, limit and remaining are null rather than a
quantity object.
Usage summary
GET /services/{id}/usage returns the current headline figures.
curl https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/usage \
-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}/usage",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=15,
)
resp.raise_for_status()
data = resp.json()["data"]
print(data["remaining"]["bytes"], data["success_rate_7d"])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}/usage`, {
headers: { Authorization: `Bearer ${API_KEY}` },
})
const { data } = await res.json()
console.log(data.remaining.bytes, data.success_rate_7d)200 response:
{
"data": {
"service_id": "clpkg_2a9x",
"is_unlimited": false,
"limit": { "bytes": "50000000000", "bytes_num": 50000000000, "gigabytes": 50 },
"used": { "bytes": "18760000000", "bytes_num": 18760000000, "gigabytes": 18.76 },
"remaining": { "bytes": "31240000000", "bytes_num": 31240000000, "gigabytes": 31.24 },
"today": { "bytes": "820000000", "bytes_num": 820000000, "gigabytes": 0.82 },
"connections_7d": 15234,
"blocked_7d": 340,
"success_rate_7d": 0.987
},
"meta": { "request_id": "req_8Ke2jP4mQ", "degraded": false }
}remaining is the authoritative quota counter, not limit minus used: the
two are measured independently (see Accounting below), so they
can drift apart by a small amount rather than always summing exactly to
limit.
blocked_7d counts connections refused on purpose over the last 7 days:
your own blocklist rules, the global destination denylist, or your
package/credential quota. Never a failure on our end. success_rate_7d
excludes those on both sides of the ratio, so enabling a blocklist preset
doesn't drag your success rate down for using it (see
Connection quality below). It's null when there were
no non-blocked attempts in the window, which includes the case where every
connection in the window was a deliberate block.
Accounting
Both directions count: usage is metered as bytes_in + bytes_out at the
proxy, not just the response body your client sees, so it includes protocol
and connection overhead your own byte counter probably doesn't. A few other
things are worth knowing before you compare this number to one you're keeping
yourself:
usedandremainingare measured independently, so in the moments right after a burst of traffic they can briefly disagree by a small amount rather than summing exactly tolimit. Both settle within a few minutes.remainingis the figure quota enforcement uses, so trust it for "can I still send traffic";usedis the one to trust for "how much have I sent".todaycan lag by a few minutes. It is aggregated rather than counted live, so traffic from the last few minutes may not appear in it yet.usedis not subject to that lag.todayis the UTC calendar day, not your local day or a rolling 24 hours. It resets at UTC midnight.- GB is decimal SI,
bytes / 1e9, the same convention as every other byte quantity in the API. If you're comparing against a tool that reports GiB (bytes / 2^30), your numbers will disagree by about 7% even when the underlying byte count matches exactly.
Why your number differs from ours: the most common causes, in order, are
counting only response bytes instead of both directions, reading today
before the last few minutes have been aggregated, and a GiB vs. GB unit
mismatch. If none of those explain the gap, compare the exact bytes string,
not gigabytes, since the latter is rounded for display.
Usage series
GET /services/{id}/usage/series returns time-bucketed usage for charts,
zero-filled across the whole range so every bucket is present.
Query parameters
| Parameter | Values | Default | Notes |
|---|---|---|---|
granularity | hour | day | day | Bucket size. |
from | ISO 8601 | now - 7 days | Start of the range. |
to | ISO 8601 | now | End of the range. |
group_by | country | - | Adds a by_country array to the response. See Breaking down by country. |
Maximum range is 180 days for day granularity and 7 days for hour.
curl "https://dashboard.proxio.net/api/v1/services/clpkg_2a9x/usage/series?granularity=day&from=2026-07-10T00:00:00Z&to=2026-07-17T00:00:00Z" \
-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}/usage/series",
headers={"Authorization": f"Bearer {API_KEY}"},
params={
"granularity": "day",
"from": "2026-07-10T00:00:00Z",
"to": "2026-07-17T00:00:00Z",
},
timeout=15,
)
resp.raise_for_status()
for point in resp.json()["data"]["points"]:
print(point["start"], point["bytes"])const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
const SERVICE_ID = "clpkg_2a9x"
const query = new URLSearchParams({
granularity: "day",
from: "2026-07-10T00:00:00Z",
to: "2026-07-17T00:00:00Z",
})
const res = await fetch(
`${BASE}/services/${SERVICE_ID}/usage/series?${query}`,
{ headers: { Authorization: `Bearer ${API_KEY}` } },
)
const { data } = await res.json()
for (const point of data.points) console.log(point.start, point.bytes)200 response:
{
"data": {
"granularity": "day",
"from": "2026-07-10T00:00:00.000Z",
"to": "2026-07-17T00:00:00.000Z",
"points": [
{
"start": "2026-07-16T00:00:00.000Z",
"bytes": "820000000",
"bytes_num": 820000000,
"gigabytes": 0.82,
"connections": 2100
}
],
"close_reasons": {
"normal": 14980, "idle_timeout": 190, "error": 41, "denied_quota": 23
}
},
"meta": { "request_id": "req_8Ke2jP4mQ", "degraded": false }
}Each point covers one bucket starting at start (UTC) and inlines a full
byte quantity: bytes, bytes_num, and gigabytes are all
present on every point, alongside connections. close_reasons aggregates why
connections ended over the range, a useful signal: denied_quota climbing means
you're running low, and a rising error count is worth investigating.
Connection quality
close_reasons and the summary's blocked_7d / success_rate_7d are built
from the same taxonomy: a connection ends for exactly one reason, and every
reason falls into one of two buckets, a deliberate block (we, or a rule
you configured, refused it on purpose) or an attempt (we actually tried to
carry it). Only attempts count toward success_rate_7d's denominator.
close_reasons keys are a closed, public vocabulary of seven values. Any
close reason outside this list is reported as error:
| Reason | Bucket | Meaning |
|---|---|---|
normal | Attempt (success) | Clean close. Carried the connection with no issue. |
idle_timeout | Attempt (success) | Established, then went quiet past the idle timeout and we closed it. Counted as a success: the connection was carried, and a timeout firing on schedule isn't a fault, but it's broken out on its own because a sudden spike usually means something upstream is stalling. |
error | Attempt (failure) | A genuine failure, including any reason not listed here. |
denied_quota | Deliberate block | The package or credential was out of quota when the connection was accepted, or ran out mid-flight. |
target_denied | Deliberate block | Our global destination denylist refused it. See Blocked Destinations. |
customer_rule | Deliberate block | Your own blocklist preset or host rule refused it. |
connection_limit | Deliberate block | Your own concurrency cap was exceeded. |
idle_timeout counts as a success
This trips people up when they add up the numbers themselves: idle_timeout
sits in the attempts denominator and is not subtracted from it, so it
never lowers success_rate_7d. It's the overwhelmingly common case of your
own idle pooled connection, not a proxy fault.
Blocks are the feature working, not the proxy failing
If you turn on a blocklist preset like ads-trackers, every refused request
shows up as customer_rule in close_reasons and counts toward
blocked_7d, not toward success_rate_7d's denominator. On a typical page
load that can be 20 to 40% of requests. Before this separation existed,
those deliberate refusals dragged the success-rate figure down and made the
feature look like an outage. Now a rising blocked_7d alongside a healthy
success_rate_7d means blocking is working as configured, not that
anything is wrong.
Breaking down by country
Add group_by=country and the response gains a by_country array beside
points. It is a breakdown of the same range, not of each bucket, so it has no
start field:
{
"data": {
"granularity": "day",
"from": "2026-07-10T00:00:00.000Z",
"to": "2026-07-17T00:00:00.000Z",
"points": [],
"close_reasons": {},
"by_country": [
{
"country": "us",
"bytes": "610000000",
"bytes_num": 610000000,
"gigabytes": 0.61,
"connections": 1580
},
{
"country": "unknown",
"bytes": "12000000",
"bytes_num": 12000000,
"gigabytes": 0.012,
"connections": 44
}
]
},
"meta": { "request_id": "req_8Ke2jP4mQ", "degraded": false }
}Entries are ordered by traffic, heaviest first, and each carries the same byte
quantity fields as a point. Traffic whose country couldn't be resolved is
grouped under "unknown". The field is present only when you pass
group_by=country; without it, by_country is absent from data rather than
empty. points and close_reasons are returned either way.
Degraded reads
If detailed usage reporting is briefly unavailable, these endpoints degrade
rather than fail: they return the best available figures and set
meta.degraded: true. Treat used and today as approximate whenever
degraded is true; remaining stays authoritative.
Related pages
Services
List your Proxio services and read connection info with GET /services and GET /services/{id}, and toggle auto-renewal with PATCH /services/{id}. A service is a package, residential services return a gateway endpoint and credential, static services return a fixed proxy list.
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.

