Vaultproxies
Customer API

API Documentation

Order plans and pull proxy lines from your own code. Available on every account, no reseller application, no approval. One header, JSON in and out, and exactly the prices you see on the site.

Introduction

The Customer API is the same platform the dashboard drives, exposed for your own code. Every account has it, you do not need to be a reseller, and there is nothing to apply for.

You can:

  • Read your balance and confirm a key works
  • List the plan catalogue with live prices
  • Place orders, paid from your account balance
  • List your active plans and their remaining bandwidth or time
  • Renew or top up a plan, read its daily usage, and rotate, suspend or lock down its credentials
  • Generate ready-to-use proxy lines with country targeting
  • Read your invoice history
Orders are paid from your balance, not a card
Top up once from the dashboard, then order as often as you like from code. Top-ups end at a hosted payment page, so that one step is not automatable, everything after it is.

Authentication

Every request carries your API key in a header. There is no OAuth flow, no token exchange and no expiry to handle.

curl
X-API-Key: VP_a1b2c3d4_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Create one in the dashboard under Account → API Keys. The key is displayed once, at creation, we store only a SHA-256 hash, so it can never be shown again. Lose it and you delete the key and make another.

A key can spend your balance
Treat it like a password. Keep it server-side, never commit it to a repository, and never ship it in a browser or mobile app where anyone can read it.

A key authenticates as you and carries no additional privilege. It cannot change your password, and it cannot create other API keys, that deliberately requires a dashboard session, so one leaked key cannot quietly multiply itself.

Managing keys

Key management is done from the dashboard, or from these endpoints using a normal logged-in session (not an API key):

EndpointDoes
GET /api/user/keysList your keys (prefixes only, never the secret)
POST /api/user/keys/createMint a key, the only response that contains the secret
POST /api/user/keys/updateRename, or disable/re-enable
POST /api/user/keys/deletePermanently revoke

You may hold up to 10 active keys. Name them after where they run (scraper-prod, staging) so you know which one to revoke without guessing. Disabling or deleting takes effect immediately on the next request.

Base URL

curl
https://vaultproxies.net/api/customer

All endpoints below live under /api/customer/* and are authenticated with X-API-Key. Requests and responses are JSON; send Content-Type: application/json on anything with a body.

Money is always integer cents, never floats. A $2.50 price is 250.

Pricing

The API charges exactly what the website charges. There is no API surcharge and no API discount.

Applied in this order:

  1. List price for the plan, from the catalogue
  2. Your loyalty tier discount, applied automatically from lifetime spend
  3. A coupon, if you pass one

What you actually paid is always invoice.amount_cents in the order response, read that rather than recomputing it yourself.

Looking for wholesale rates?
Wholesale is a separate, approval-gated surface at /api/reseller/*. An ordinary key gets 403 there. See the Reseller API reference.

Check your balance

GET/api/customer/me

The cheapest call in the API, and the right health check: it proves the key works and tells you what you have to spend.

curl
curl -s https://vaultproxies.net/api/customer/me \
  -H "X-API-Key: $KEY"
curl
{
  "id": 42,
  "email": "[email protected]",
  "username": "acme",
  "balance_cents": 5000,
  "balance_locked": false,
  "wholesale_pricing": false
}
FieldMeaning
balance_centsWhat orders draw from
balance_lockedTrue = orders refused pending a support review; the balance shows but cannot be spent
wholesale_pricingTrue only for approved resellers

If an order is refused while you believe there is money on the account, check balance_locked first. A locked balance is refused with 403 and a message asking you to contact support, not with insufficient balance.

List plans

GET/api/customer/plans

The live catalogue with current retail pricing. Read it rather than hardcoding prices, they change.

curl
{
  "categories": [
    {
      "id": 1,
      "key": "resi_pergb",
      "display_name": "Residential /GB",
      "pricing_type": "pergb",
      "unit": "GB",
      "price_per_unit_cents": 100,
      "min_units": 1,
      "max_units": 1000
    }
  ]
}
FieldMeaning
keyWhat you pass as category_key when ordering
pricing_typepergb = billed per GB · unlim = billed per unit of time · perip = billed per dedicated IP for a fixed term
unitGB, HOUR, DAY or IP, tells you what units means
min_units / max_unitsAccepted range for a single order

Create an order

POST/api/customer/order
curl
curl -s -X POST https://vaultproxies.net/api/customer/order \
  -H "X-API-Key: $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"category_key":"resi_pergb","units":10}'
curl
{
  "service": {
    "id": 5567,
    "category_id": 1,
    "purchased_units": 10,
    "status": "active",
    "remaining_gb": 10,
    "username": "vp_a1b2c3",
    "password": "s3cr3t"
  },
  "invoice": { "id": 8822, "amount_cents": 1000, "status": "paid" },
  "coupon_applied": "SAVE10",
  "discount_cents": 100,
  "original_cents": 1100
}

The service object carries no price. What you paid is invoice.amount_cents in the same response, as Pricing above says.

Residential Unlimited is cheaper by the hour the longer you buy: an order (or an extension, by the hours it adds) long enough to reach a bulk rung is discounted on the whole order, before any loyalty discount or coupon. The response then also carries duration_discount_percent, duration_discount_cents and list_cents. GET /api/customer/unlimited/speeds lists the rungs as duration_discounts (min_hours, percent_off), and each pool says whether they apply to you in duration_discount.

You are not charged if provisioning fails
The balance deduction and the provisioning run inside one transaction. If a new plan cannot be provisioned, nothing is deducted and no service is created, so there is no half-state to reconcile: you get a 500 carrying a support reference to quote. A failed extend is a 502 with its own reference, and that plan is not charged either. Something you can fix yourself, such as a missing or unknown pool, comes back as a plain 400 naming it, with no reference.

To top up or renew an existing plan instead of creating a second one, use Renew or top up. It keeps the plan's credentials, and its IPs on dedicated ISP.

List your plans

GET/api/customer/services
curl
{
  "services": [
    {
      "id": 5567,
      "category_id": 1,
      "status": "active",
      "purchased_units": 10,
      "remaining_gb": 7.4,
      "end_at": null,
      "username": "vp_a1b2c3",
      "password": "s3cr3t"
    }
  ]
}
FieldMeaning
remaining_gbSet on per-GB plans, bandwidth left
end_atSet on time-based plans, when it expires
statusactive, expired, suspended, recycled, restoring or forfeited
username / passwordThe plan's proxy credentials

Only active passes traffic; every other status is the reason a line stopped working. suspended is reversible and leaves the plan intact, but the clock keeps running: a time-based plan carries on expiring while it is suspended. recycled means the credentials were reclaimed while the plan sat unused, with the unspent units still on it: the response adds restorable, restorable_units and a plain-English status_message, and the re-issue is free from the dashboard. restoring is that re-issue in flight, and forfeited is a reclaimed plan that can no longer be restored.

Manage a plan

Everything a customer needs after the sale: renewing, topping up, usage, credentials and access. Every call below takes the service_id from List your plans, and acts only on plans that belong to your key.

Renew or top up

POST/api/customer/services/extend
curl
curl -s -X POST https://vaultproxies.net/api/customer/services/extend \
  -H "X-API-Key: $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"service_id":5567,"units":10}'
Plan typeWhat units meansResult
Per-GB plansGB to addAdded to remaining_gb on the same credentials
Unlimited (time) plansHours, or pass time_unit day / week / monthAdded to end_at; an expired plan comes back with the same credentials
Dedicated ISPIgnored: every IP on the plan is renewedSame IPs; term_days, a multiple of 30 up to 360 (default 30), added to end_at

It is charged from your balance at the same price as a new order of that size, and answers like Create an order. A dedicated ISP plan can be renewed while it is active; once it has ended its IPs are released and it needs a new order.

GET/api/customer/services/extendable?service_id=5567
curl
{
  "service_id": 5567,
  "extendable": true,
  "reason": "",
  "category_key": "resi_pergb",
  "unit": "GB",
  "min_units": 1,
  "max_units": 10000,
  "price_per_unit_cents": 100
}

Ask this before showing a renew button: when extendable is false, reason says why in plain English.

Usage per day

GET/api/customer/services/usage?service_id=5567&days=30
curl
{
  "from": "2026-09-03",
  "to": "2026-10-02",
  "days": 30,
  "total_gb": 41.2,
  "history_starts": "2026-08-14",
  "points": [ { "date": "2026-10-01", "gb": 1.8 }, { "date": "2026-10-02", "gb": 0.6 } ]
}

Days are UTC and a day without traffic is a zero, not a gap. Use from and to (YYYY-MM-DD) instead of days for a fixed range. Leave out service_id to get the whole account, with each plan's total for the range.

Credentials and access

All of these are POST with a JSON body containing service_id.

EndpointBodyWhat it does
/api/customer/services/passwordpassword (optional)Rotates the plan's password; leave it out to have a strong one generated. Returns the new username and password. The old ones stop working.
/api/customer/services/suspendnoneStops the plan authenticating, for example when an end customer stops paying. Nothing is refunded and a time-based plan keeps its clock running.
/api/customer/services/resumenoneUndoes a suspend.
/api/customer/services/allowed-ipsips (array, up to 10)Replaces the list of IPs allowed to use the plan; an empty array goes back to username and password only. Not available on plans served through our gateway, where the credentials are the access control.
/api/customer/services/updatelabelYour own name for the plan, for example your customer's order number. Send an empty string to clear it.
/api/customer/services/deleteconfirm: trueDeletes the plan and forfeits whatever is left on it. Irreversible, which is why it needs confirm.

Generate proxy lines

POST/api/customer/proxies/generate

Turns a plan into ready-to-paste proxy lines with the targeting you ask for.

curl
curl -s -X POST https://vaultproxies.net/api/customer/proxies/generate \
  -H "X-API-Key: $KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "service_id": 5567,
    "plan_key": "resi_pergb",
    "country": "us",
    "protocol": "HTTP",
    "format": "ip:port:user:pass"
  }'
FieldNotes
service_idRequired. From /api/customer/services
plan_keyRequired. The plan's category key
countryISO-2, lowercase. Mutually exclusive with region
regionContinent code (EU, NA, SA, AS, AF, OC) or a grouping like nordics, dach, apac, latam
state, cityFiner targeting, where the plan supports it
protocolHTTP (default) or SOCKS5
formatDefault ip:port:user:pass
gateOptional. auto (default) or an id from the gates array of GET /api/plans/catalog: that array is the live list, and an id not in it is refused. Moves the lines to that gate; ports and credentials stay the same, and so does the exit IP. auto is the plan's recommended gate, closest to the plan's infrastructure, and is fastest for almost everyone. Refused on plans without a gate choice (gate_selectable in the catalog)
ipsStatic IP list, IP-targeted shared_isp plans only
Targeting support varies by plan
GET /api/plans/catalog is public and lists which targeting keys each plan accepts, along with its gateway host and ports. Check there rather than guessing. Most unsupported keys are refused outright, but a few are dropped and the request still succeeds when something coarser on the same credential can still be targeted, so read the credentials you get back rather than assuming every key you sent was applied.

Targeting tokens

Proxy usernames carry their targeting inline, as -key-value pairs appended to the base username. The generator builds these for you; this table is for hand-built credentials.

curl
ca3ddbbd85591e7f-geo-us-st-california-sess-drfse9co-life-21600
TokenSelectsExample
geo-Country, ISO-3166 alpha-2geo-us
st-State or provincest-california
reg-A group of COUNTRIES, not a statereg-nordics
pc-Postal / ZIP codepc-90001
dma-Metro area (Nielsen DMA)dma-501
loc-City, by name from the locations endpointloc-dallas
gid-City, by GeoNames idgid-5368361
asn-Autonomous system numberasn-7922
isp-ISP or mobile carrierisp-comcast
tz-Timezonetz-america_new_york
near-Radius search, lat, long, kmnear-34.05N118.24W50
ip-Pin one static IP (dots as underscores)ip-203_0_113_7
sess-Sticky session id, same exit IP while it livessess-a1b2c3d4
life-Session lifetime, in secondslife-600
reg- is not a state
reg- selects a group of countries (nordics, africa…). For a US state or a Canadian province use st-, st-california, not reg-california. geo- and reg- both pick the country, so pairing geo- with a real region name is rejected rather than one being silently dropped. Credentials we issued before st- existed spelled the state reg-; those keep working, but st- is what to use now.

A plan only honours the tokens its pool can actually act on, and a token it does not support is normally refused outright rather than dropped, so a credential never quietly lands somewhere coarser than you asked for. The one deliberate exception is a city name on a pool that accepts cities only by id: if a country or state on the same credential still gives something to target with, the city is dropped and the line is served at that level instead of failing the whole batch. A city name with nothing else alongside it has nothing to fall back to, and is still refused. On resi_lite_pergb a city is gid-; on resi_pergb and the Main pool it is loc-, a name from the locations endpoint.

PlanTokens it honours
resi_pergbgeo, reg, loc, asn, sess, life, a catalogued city by name, sticky up to 3 hours
resi_lite_pergbgeo, reg, st, pc, dma, gid, asn, isp, tz, near, sess, life, a city is gid, never a name, sticky up to 6 hours
mobile_pergbgeo, reg, asn, sess, life
dc_pergbgeo, reg, sess, life, US-only pool
shared_ispgeo, ip, sess, life, US-only pool
ipv6_pergbNone, US-only pool, nothing left to choose
eu_ispgeo, NL-only pool; no sticky, it rotates regardless of session
dedicated_ispNone, the IP you were assigned is the targeting
resi_unlim (Main pool)geo, loc, asn, sess, life, a catalogued city by name, sticky up to 3 hours
resi_unlim (Legacy pool), resi_unlim_budget, dc_unlimA different username grammar, none of the tokens above apply; let the dashboard generator or POST /api/customer/proxies/generate build these credentials for you

GET /api/plans/catalog is public and publishes the targeting dimensions each plan supports, alongside each plan’s gateway host and ports. It names dimensions rather than the short tokens above, and is narrower than this table in places, so read it from your code rather than hard-coding either.

Sticky usernames, per plan

The tokens above are the grammar of the plans served through our own gateway. The rest each speak their own, and an unknown keyword is ignored rather than refused, so a credential that looks right can quietly hold no session at all. If you build usernames by hand, copy the shape for your plan exactly; the keyword and the unit both change from plan to plan.

PlanSticky usernameSession length
resi_pergbuser-geo-us-loc-<city>-sess-<id>-life-<seconds>Seconds, up to 10800 (3 h). The city is optional and must be one the locations endpoint lists
resi_lite_pergbuser-geo-us-st-<state>-sess-<id>-life-<seconds>Seconds, up to 21600 (6 h). The state is optional
pool4_pergbuser-geo-us-sess-<id>Fixed hold of about 10 minutes, no duration token
mobile_pergbuser-country-us-session-<id>-time-<minutes>MINUTES, up to 120 (2 h)
shared_ispuser-country-us-time-<seconds>-session-<id>Seconds, up to 86400 (24 h). Or pin one IP with -ip-203_0_113_10, which cannot be combined with a session
eu_ispuser-country-nlNo sticky sessions: this pool rotates whatever you send
ipv6_pergbuserNo targeting and no sticky sessions
resi_unlim (Main pool)user-geo-us-loc-chicago-sess-<id>-life-<seconds>Seconds, up to 10800 (3 h)
resi_unlim (Legacy pool)user-country-us-session-<8 digits>-time-<minutes>MINUTES, not seconds, up to 1440 (24 h). The session id must be digits only
resi_unlim_budgetuser-session-<8 digits>-ttl-<seconds>-country-usSeconds, up to 86400 (24 h). Country is optional and may be omitted
dc_unlimuser-session-<id>-duration-<seconds>Seconds, up to 86400 (24 h). The keyword is duration, not ttl
dedicated_ispuserThe IP you were assigned is the targeting

POST /api/customer/proxies/generate builds all of these for you from mode, extra.session_seconds and a country, and converts the unit each plan expects. Use it and you never have to track the differences above.

Invoices

GET/api/customer/invoices

Your payment history, top-ups and plan purchases alike. Internal anti-fraud fields are stripped before the response leaves the server.

Order parameters

FieldTypeNotes
category_keystringRequired. From /api/customer/plans
unitsintRequired. GB, or time units for unlimited plans
time_unitstringhour | day | week | month, time-based plans only. Anything else is rejected
couponstringOptional discount code
update_service_idintExtend an existing plan instead of creating a new one
term_daysintdedicated_isp only. Any whole multiple of 30 days, up to 360. Leave it out and you get 30; send a value that is not a whole multiple in range and the order is rejected
poolstringdedicated_isp only, and required there: the order is rejected without it, and also if the pool is unknown or short of stock for the IP count you asked for

Pool ids come from GET /api/proxy/isp-pools, which lists each pool with its live stock. That endpoint takes a logged-in session rather than an API key, the same as the key endpoints above, so pick the pool from the dashboard or from a session call and pass the id you get back.

Error responses

Errors are a JSON object with an error string and a 4xx/5xx status. The message is safe to show a user.

curl
{ "error": "insufficient balance" }
StatusErrorWhat to do
401invalid api keyKey is wrong, disabled or deleted
402insufficient balanceThe order costs more than balance_cents, top up
400coupon is no longer validExpired, used up, or wrong category
400category_key and units requiredMissing a required field
400pool is required for dedicated ISP orders…A problem in your payload, named in full. No support reference, because there is nothing for support to look up, fix the field and resend
403account suspendedContact support
403Your balance has been locked for security reasons…The balance shows but cannot be spent, contact support
403reseller access not approvedYou hit /api/reseller/* with an ordinary key
429rate limit exceeded, please try again laterBack off and retry
500order could not be completed — you have not been charged. Contact support with reference …A new plan could not be provisioned. Nothing was deducted; quote the reference
502extend could not be completed — the plan was not charged. Contact support with reference …An update_service_id top-up could not be applied. The plan is untouched and unbilled; quote the reference
500order failedTransient, retry with backoff

Rate limits

SurfaceLimit
/api/customer/*7,200 requests / minute, per API key, a burst budget of about 120 a second
Order creationNo extra cap, it draws on the same per-key budget as every other call
Top-ups (dashboard)3 payment invoices / minute, per account. This one is not on the API surface
/api/plans/catalog (public)60 / minute per IP, shared with the other public endpoints

The customer surface is metered per key and shared across every /api/customer/* endpoint, so a noisy integration cannot rate-limit your other keys. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, so you can pace yourself without guessing. Exceeding a limit returns 429 with Retry-After, back off and retry rather than hammering.

Quick start

End to end: check the key, buy 10 GB, pull proxy lines.

curl
BASE=https://vaultproxies.net
KEY=VP_a1b2c3d4_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
H="X-API-Key: $KEY"

# 1. Does the key work, and what can I spend?
curl -s $BASE/api/customer/me -H "$H" | jq '{balance_cents, balance_locked}'

# 2. What can I buy?
curl -s $BASE/api/customer/plans -H "$H" \
  | jq '.categories[] | {key, price_per_unit_cents}'

# 3. Buy 10 GB of residential
SVC=$(curl -s -X POST $BASE/api/customer/order -H "$H" \
  -H 'Content-Type: application/json' \
  -d '{"category_key":"resi_pergb","units":10}' | jq -r .service.id)

# 4. Generate proxy lines for it
curl -s -X POST $BASE/api/customer/proxies/generate -H "$H" \
  -H 'Content-Type: application/json' \
  -d "{\"service_id\":$SVC,\"plan_key\":\"resi_pergb\",\"country\":\"us\",\"protocol\":\"HTTP\"}"
python
import requests

BASE = "https://vaultproxies.net"
H = {"X-API-Key": "VP_a1b2c3d4_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}

me = requests.get(f"{BASE}/api/customer/me", headers=H).json()
print("balance:", me["balance_cents"] / 100)

order = requests.post(
    f"{BASE}/api/customer/order",
    headers={**H, "Content-Type": "application/json"},
    json={"category_key": "resi_pergb", "units": 10},
).json()

proxies = requests.post(
    f"{BASE}/api/customer/proxies/generate",
    headers={**H, "Content-Type": "application/json"},
    json={
        "service_id": order["service"]["id"],
        "plan_key": "resi_pergb",
        "country": "us",
        "protocol": "HTTP",
    },
).json()
print(proxies)
    Customer API Documentation: Proxy Generation & Management | VaultProxies