API Reference

T-Proxy Reseller API v1

Programmatic access to buy, manage, and monitor UK mobile proxies. This API covers UK stock only — it does not expose USA inventory.

Base URL: https://t-proxy.net/api/v1

Authentication Quickstart Errors Rate limits GET /account GET /plans GET /stock POST /coupons/validate POST /proxies GET /proxies GET /proxies/:id WireGuard & Vless POST /proxies/:id/rotate POST /proxies/:id/reboot POST /proxies/:id/extend GET /proxies/:id/ovpn POST /proxies/:id/spoof POST /proxies/:id/ip-mode POST /proxies/:id/credentials POST /proxies/:id/auto-renew

Authentication

Every request must include your API key in the x-api-key header. Generate or revoke your key from My Account — reseller-tier accounts only, one active key per account.

curl -H "x-api-key: tpk_live_..." https://t-proxy.net/api/v1/account

Keys never expire on their own. Regenerating a key immediately invalidates the previous one. If your account is downgraded from reseller status, the key stops working even though the row itself still exists.

Quickstart

A typical integration: check what's in stock, buy one, then keep it running.

KEY="tpk_live_..."
BASE="https://t-proxy.net/api/v1"

# 1. See what's available right now
curl -s -H "x-api-key: $KEY" "$BASE/stock"

# 2. Buy a 4G EE proxy for a week
curl -s -X POST -H "x-api-key: $KEY" -H "Content-Type: application/json" \
  -d '{"carrier":"EE","planCode":"4g_week"}' "$BASE/proxies"
# → { "proxies": [ { "id": 501, "proxy_username": "...", ... } ] }

# (Need WireGuard or Vless? Check "$BASE/stock?wireguard=1" and add
#  "deviceFilter":{"wireguard":true} to the claim - see WireGuard & Vless below.)

# 3. Check on it later
curl -s -H "x-api-key: $KEY" "$BASE/proxies/501"

# 4. Rotate its IP whenever you need a fresh one
curl -s -X POST -H "x-api-key: $KEY" "$BASE/proxies/501/rotate"

# 5. Extend it before it expires
curl -s -X POST -H "x-api-key: $KEY" -H "Content-Type: application/json" \
  -d '{"planCode":"4g_week"}' "$BASE/proxies/501/extend"

Stock counts are a snapshot, not a reservation — a claim can still 409 if another order takes the last modem between your stock check and your claim. Treat 409 on claim as "retry", not an error to surface to your own users unchanged.

Errors

All errors return JSON: { "error": "..." }, with a relevant HTTP status code:

StatusMeaning
400Bad request — missing/invalid field
401Missing, invalid, or revoked API key
402Insufficient account balance
403Key is valid but the account no longer has reseller access
404Proxy not found, or not owned by this account
409Conflict — e.g. stock changed mid-order, or the action isn't available for this proxy yet
429Rate limit exceeded — see below
502The UK database (upstream provider) failed to respond as expected

Rate limits

Limits apply per account (i.e. per API key), not per IP. Each tier below is tracked independently, so exhausting one doesn't affect the others. Standard RateLimit-* response headers report your remaining quota on every call.

TierEndpointsLimit
ReadGET /account, /plans, /stock, /proxies, /proxies/:id, /proxies/:id/ovpn120 / minute
ActionPOST /proxies/:id/rotate, /reboot, /spoof, /ip-mode, /credentials, /auto-renew20 / minute
PurchasePOST /proxies, /proxies/:id/extend, /coupons/validate10 / minute

A 429 includes how long to wait via the RateLimit-Reset header (seconds). If your integration needs a higher ceiling, get in touch rather than working around this.

Account

GET/api/v1/account

Returns your balance and reseller status.

{ "email": "you@example.com", "balance": 42.50, "is_reseller": true }

Plans & stock

GET/api/v1/plans

Lists every UK fixed-term plan and its price at your account's tier (reseller pricing is applied automatically). Pay & Go is discontinued and never appears here.

{
  "plans": [
    { "code": "4g_day", "label": "4G — 1 day", "generation": "4g", "term": "day", "price_amount": 2.5, "price_unit": "/day" },
    { "code": "5g_month", "label": "5G — 1 month", "generation": "5g", "term": "month", "price_amount": 50, "price_unit": "/month" }
  ]
}
GET/api/v1/stock

Live availability per carrier. Counts reflect modems free right now — they change constantly and aren't reserved by checking them.

{
  "carriers": [
    { "carrier": "EE", "available_4g": 0, "available_5g": 1, "has_ipv6": true, "has_vless": true, "has_wireguard": true, "has_phone": true, "has_router": true,
      "next_available_4g": "2026-10-05T21:30:00.000Z", "next_available_5g": null,
      "pending": [ { "generation": "4g", "device": "router", "ipv6": true, "vless": true, "wireguard": true, "available_at": "2026-10-05T21:30:00.000Z" } ] }
  ]
}

Carriers: EE, O2, Three, Sky, Lyca.

Query parameters (all optional)

ParameterValuesNotes
ipv61 / trueOnly count modems with IPv6
vless1 / trueOnly count modems with VLESS (Xray)
wireguard1 / trueOnly count modems with WireGuard
devicephone / routerOnly count that device type

Example: GET /api/v1/stock?wireguard=1&device=router. Filters narrow available_4g, available_5g, next_available_* and pending — the same as the filter chips on the Browse page. The has_* fields always describe what the carrier can do at all, whatever the filters.

next_available_4g / next_available_5g are the earliest time (ISO, UTC) a currently rented proxy of that generation is due to free up, or null when none is scheduled — the same estimate as the "until next slot" countdown on the Browse page. pending lists every rented proxy for the carrier, soonest first, each with its device, IPv6, VLESS and WireGuard flags. These are estimates: a proxy that is extended or auto-renews before then won't free up.

Coupons

POST/api/v1/coupons/validate

Previews a coupon's discount without using it. Coupons are applied by passing couponCode to POST /proxies or POST /proxies/:id/extend; a coupon is only used up when that order goes through.

Body

FieldTypeRequiredNotes
codestringyesThe coupon code
planCodestringyesFrom GET /plans
quantityintegernoDefault 1; ignored for "extend"
contextstringno"claim" (default) or "extend"
{ "valid": true, "code": "BETA25", "discount_type": "percent", "discount_value": 25, "order_total": 40, "discount_amount": 10, "final_amount": 30 }

An invalid, expired or used-up code returns 400 with { "valid": false, "error": "..." }. Prices are your own account's prices, and the discount comes off the whole order.

Trial coupons (discount_type: "trial") make a new order free, but each proxy in it runs for 2 hours, whatever the plan's term. They can't be used on extensions.

Claiming a proxy

POST/api/v1/proxies

Buys one or more proxies on a given carrier and plan. Charges your account balance immediately; fails with 402 if your balance is too low.

Body

FieldTypeRequiredNotes
carrierstringyesOne of the five carriers above
planCodestringyesFrom GET /plans
quantityintegernoDefault 1, max 10 — number of separate proxies to claim
deviceFilterobjectno{ "phone": bool, "router": bool, "ipv6": bool, "vless": bool, "wireguard": bool } — require a capability; omit a field to accept either. Use the same filters on GET /stock to check there's a match first
ipModestringno"" (default, dual-stack) or "64" (prefer IPv6 first)
couponCodestringnoA coupon code (not case-sensitive), applied to the whole order. See coupons
curl -X POST https://t-proxy.net/api/v1/proxies \
  -H "x-api-key: tpk_live_..." -H "Content-Type: application/json" \
  -d '{"carrier":"EE","planCode":"4g_week","quantity":2}'

Returns 201 with { "proxies": [...], "charged": 30, "discount_amount": 10, "coupon": "BETA25" } (coupon is null without one). For the proxy shape, see proxy detail. If stock runs out mid-order, whatever was already claimed is rolled back and you're charged nothing; retry with a smaller quantity.

Listing & detail

GET/api/v1/proxies

Lists every active proxy on your account.

{ "proxies": [ { "id": 501, "plan": "4g_week", "carrier": "EE", "proxy_username": "...", "proxy_password": "...", "http_port": 8123, "socks_port": 5123, "connect_host": "203.0.113.10", "rotation_url": "https://...", "vless_link": "vless://…", "wireguard_link": "wireguard://…", "ovpn_link": "https://t-proxy.net/api/proxies/public/ovpn/…", "auto_renew": false, "expires_at": "2026-09-24T12:00:00.000Z", "created_at": "2026-09-17 10:00:00" } ] }

vless_link and wireguard_link are null on proxies whose modem doesn't support them. See WireGuard & Vless for how to get them and turn wireguard_link into a config file.

ovpn_link is a shareable OpenVPN profile download that needs no login, so you can pass it straight to your own customer. Anyone with the link can download the profile; the proxy's username and password are still needed to connect.

GET/api/v1/proxies/:id

Same shape as above, plus live fields fetched from the UK database on each call:

{ "proxy": { ...as above,
  "current_ip": { "ipv4": "203.0.113.55", "ipv6": null },
  "spoof": { "current": "", "options": [ { "value": "", "label": "No spoofing (default)" }, { "value": "windows:1", "label": "Windows 10" }, ... ] },
  "ip_mode": { "current": "v4", "ipv6Available": true },
  "data_quota_mb": null,
  "data_usage_mb": 20377.6 } }

data_usage_mb is total traffic (download + upload) since the proxy was created. data_quota_mb is null on all fixed-term plans, which have no data cap. rotation_url is refreshed here too, so use the value from this call if yours stops working. Any live field is null (or its default) if the UK database is briefly unreachable.

WireGuard & Vless

Most of our modems also support WireGuard and Vless (Xray). Each proxy on one of them has a wireguard_link and a vless_link in GET /proxies and GET /proxies/:id; on other proxies both are null.

Getting a proxy that has them

Check stock with GET /stock?wireguard=1 (or ?vless=1), then claim with "deviceFilter": { "wireguard": true } (or "vless": true). With the filter set, a claim only ever lands on a modem that has it. Without it you may get one that doesn't.

curl -s -H "x-api-key: $KEY" "$BASE/stock?wireguard=1"
curl -s -X POST -H "x-api-key: $KEY" -H "Content-Type: application/json" \
  -d '{"carrier":"EE","planCode":"4g_week","deviceFilter":{"wireguard":true}}' "$BASE/proxies"

The links

FieldFormatUse it with
wireguard_linkwireguard://PRIVATEKEY@HOST:PORT?publickey=…&address=…&presharedkey=…&remoteDNS=…Apps that import wireguard:// links (Hiddify, v2rayN, NekoBox), or convert it to a .conf for the official WireGuard apps (below)
vless_linkvless://…Xray apps such as v2rayNG, Hiddify, Streisand or Shadowrocket: import from clipboard

Neither link needs the proxy's username or password: anyone who has one can use the proxy, so treat both like passwords and give each customer only their own. Both change when a proxy is migrated, so read them again after a migration. The keys in wireguard_link are plain base64 and can contain /, + and =, so most URL parsers misread it — split it by hand.

Make a WireGuard .conf file (Python)

import requests

KEY = "tpk_live_..."
BASE = "https://t-proxy.net/api/v1"

def wireguard_conf(link):
    # wireguard://PRIVATEKEY@HOST:PORT?publickey=...&address=...&presharedkey=...&remoteDNS=...
    # The keys are plain base64 (they can contain / + =), so split by hand
    # rather than with a URL parser.
    key, _, rest = link[len("wireguard://"):].partition("@")
    endpoint, _, query = rest.partition("?")
    params = dict(p.split("=", 1) for p in query.split("&") if "=" in p)
    lines = ["[Interface]",
             f"PrivateKey = {key}",
             "Address = " + params["address"].replace(",", ", ")]
    if params.get("remoteDNS"):
        lines.append(f"DNS = {params['remoteDNS']}")
    lines += ["", "[Peer]", f"PublicKey = {params['publickey']}"]
    if params.get("presharedkey"):
        lines.append(f"PresharedKey = {params['presharedkey']}")
    lines += [f"Endpoint = {endpoint.rstrip('/')}",
              "AllowedIPs = 0.0.0.0/0, ::/0",
              "PersistentKeepalive = 25"]
    return "\n".join(lines) + "\n"

proxy = requests.get(f"{BASE}/proxies/501", headers={"x-api-key": KEY}, timeout=60).json()["proxy"]
if proxy["wireguard_link"]:
    with open(f"tproxy-{proxy['id']}.conf", "w") as f:
        f.write(wireguard_conf(proxy["wireguard_link"]))
else:
    print("No WireGuard on this proxy - claim with deviceFilter.wireguard")

This writes the same file as the WireGuard button on the proxy page:

[Interface]
PrivateKey = …
Address = 10.2.1.15/32, fd78:…::1:15/128
DNS = …

[Peer]
PublicKey = …
PresharedKey = …
Endpoint = server2.t-proxy.net:444
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

WireGuard connects over UDP port 444. Each config is one WireGuard peer, so use it on one device at a time.

Actions

POST/api/v1/proxies/:id/rotate

Requests a new IP on the modem. Returns { "ok": true, "ext_ip": "203.0.113.99" }.

POST/api/v1/proxies/:id/reboot

Reboots the physical device. Returns { "ok": true, "message": "..." }. Expect the proxy to be briefly unreachable afterward.

POST/api/v1/proxies/:id/extend

Extends a proxy's expiry by charging for another term. Body: { "planCode": "4g_week", "couponCode": "BETA25" } (couponCode optional) — planCode must match the proxy's current generation (4G proxies can only extend onto 4G plans, same for 5G).

{ "ok": true, "expires_at": "2026-10-01T12:00:00.000Z", "charged": 15, "discount_amount": 5, "coupon": "BETA25", "plan": "4g_week" }
GET/api/v1/proxies/:id/ovpn

Downloads the proxy's OpenVPN profile (Content-Type: application/x-openvpn-profile). Same file as ovpn_link, but behind your API key.

POST/api/v1/proxies/:id/spoof

Sets the OS / TCP fingerprint the proxy presents. Body: { "os": "windows:1" }, using a value from spoof.options on proxy detail; "" turns spoofing off. os is required.

The profile applies to IPv4 connections to ports 80, 443 and 8443; IPv6 connections and other ports keep the device's own fingerprint. If the spoofed fingerprint matters, set the proxy to prefer IPv4 with POST /proxies/:id/ip-mode and { "mode": "v4" } — otherwise sites that support IPv6 are reached over IPv6 and see the unspoofed fingerprint.

{ "ok": true, "os": "windows:1", "rotation_url": "https://..." }
POST/api/v1/proxies/:id/ip-mode

Chooses whether the proxy prefers IPv4 or IPv6. Body: { "mode": "v4" } or { "mode": "v6" }. Both stay reachable; this only sets the default. "v6" returns 409 if ip_mode.ipv6Available is false, or if the SIM has no IPv6 right now.

{ "ok": true, "mode": "v6", "rotation_url": "https://..." }
POST/api/v1/proxies/:id/credentials

Sets your own username or password for HTTP, SOCKS5 and OpenVPN. Body: { "field": "password", "value": "MyPass_123" }, where field is "username" or "password" and value is 3–32 letters, numbers, _ or -. Anything connected with the old login is disconnected.

{ "ok": true, "proxy_username": "...", "proxy_password": "MyPass_123", "rotation_url": "https://..." }

The rotation URL can change when credentials change. Replace your stored copy with the one returned here.

POST/api/v1/proxies/:id/auto-renew

Turns automatic renewal on or off. Body: { "enabled": true }. When on, the proxy is renewed for another term of its current plan from your balance shortly before it expires.

{ "ok": true, "auto_renew": true }