For authentication, error format, and common headers, see General API Info.
Orders
Get Proxy Catalog Config
GET /orders/config
Get the full proxy catalog config. Public — no auth required.
Response
{
"proxyTypes": [
{
"id": "residential-giga",
"name": "Residential Giga",
"slug": "residential-giga",
"shortDescription": "Real residential IPs, pay per Giga — maximum trust.",
"paymentModel": "prepaid",
"unit": "giga",
"pricePerUnit": 1.5,
"hasTraffic": true,
"trafficDiscounts": [
{ "min": 10, "discount": 0.05, "label": "5% off" }
],
"countDiscounts": [],
"tags": ["Rotating IP", "Real ISP"],
"countryMode": "anytime",
"requiresCountry": false,
"supportsCountry": true,
"supportsCity": true,
"supportsState": true,
"supportsChangeCountry": true,
"billingPeriods": [],
"presets": [
{ "label": "Trial", "trafficGb": 1 }
]
},
...
],
"billingPeriods": [
{ "id": "week", "label": "Weekly", "multiplier": 0.4167 },
{ "id": "month", "label": "Monthly", "multiplier": 1 },
{ "id": "year", "label": "Yearly", "multiplier": 10.5 }
],
"minOrderAmount": 0.5,
"trafficPricePerGb": 0.10,
"trafficVolumeDiscounts": [
{ "min": 50, "discount": 0.05, "label": "5% off traffic" },
{ "min": 100, "discount": 0.10, "label": "10% off traffic" },
{ "min": 500, "discount": 0.15, "label": "15% off traffic" }
]
}Preview Order Pricing Auth required
POST /orders/preview
Preview order pricing before buying.
Request Body
Prepaid giga:
{
"proxyTypeId": "residential-giga",
"trafficGb": 50
}Postpaid IP:
{
"proxyTypeId": "private-proxy",
"count": 25,
"trafficGb": 50,
"billingPeriod": "month",
"countries": { "US": 10, "DE": 10, "GB": 5 }
}| Name | Type | Description | Required |
|---|---|---|---|
proxyTypeId | string | Proxy type ID from config | Yes |
trafficGb | number | GB of traffic | Giga types: Yes |
count | number | Number of IPs | IP types: Yes |
billingPeriod | string | week / month / year | No |
countries | object | Country → count map | No |
Response
{
"proxyType": {
"id": "residential-giga",
"name": "Residential Giga",
"paymentModel": "prepaid",
"unit": "giga"
},
"breakdown": {
"proxyTypeId": "residential-giga",
"count": 0,
"trafficGb": 50,
"subtotal": 63.75,
"discountAmount": 11.25,
"total": 63.75
},
"display": {
"totalDisplay": 2637.19,
"currency": "UAH",
"exchangeRate": 41.37
}
}A type with
"hidden": true(the free test, idfree) is never sold:previewandcreaterefuse it withorder.invalid_proxy_type, and it cannot be renewed or topped up. It is listed so clients can show orders that use it; do not offer it for purchase.
Get Free Test Status Auth required
GET /orders/free-test
Whether the account can still claim its one free test, and what the test gives.
Response
{ "available": true, "claimed": false, "orderId": null, "ips": 3, "trafficGb": 1, "hours": 24 }claimed— the account already had it (a deleted or expired one counts; one an admin cancelled does not)orderId— hash id of its free test order, while that order still existsavailable—truewhen it can be claimed now (not claimed, and the account is not flagged as a bot)
Claim Free Test Auth required 5 req / 24h
POST /orders/free-test
Gives 3 server IPs from 3 random countries and 1 GB of traffic for 24 hours, free. One per account. Rate limit: 5 per day per IP.
Response
Full order object (same as GET /orders/:id) with proxyType.id: "free", billingPeriod: null, dates.expiresAt 24 hours ahead and pricing.totalUsd: 0. Auto-renew is off and cannot be paid for.
Errors
{ "message": "free_test.already_claimed" }
{ "message": "free_test.unavailable" }
{ "message": "free_test.out_of_stock" }
{ "message": "free_test.rate_limit" }On out_of_stock nothing is created and the account may try again later. The funnel records a free_test_claimed event (not order_placed).
Create Order Auth required
POST /orders/create
Create a real order. Both prepaid and postpaid orders deduct balance immediately.
Request Body
Postpaid with selected countries:
{
"proxyTypeId": "private-proxy",
"count": 25,
"trafficGb": 50,
"billingPeriod": "month",
"countries": { "US": 10, "DE": 10, "GB": 5 }
}Postpaid with random countries:
{
"proxyTypeId": "private-proxy",
"count": 25,
"trafficGb": 50,
"billingPeriod": "month"
}When
countriesis omitted ornull, IPs are assigned from random available countries. Country selection does not affect price.
Response
Full order object (same as GET /orders/:id).
Errors
{ "message": "order.invalid_proxy_type" }
{ "message": "order.invalid_billing_period" }
{ "message": "order.invalid_count" }
{ "message": "order.invalid_traffic_gb" }
{ "message": "order.countries_required" }
{ "message": "order.insufficient_balance" }Get Orders Summary Auth required
GET /orders/summary
Lightweight summary — single aggregation query, no pagination. Returns order counts, total IPs, traffic purchased, and amount spent — grouped by status and proxy type. Ideal for dashboard badges, spending overviews and per-section counters.
Response
{
"total": {
"orders": 7,
"ips": 35,
"trafficGb": 120.5,
"spentUsd": 89.40
},
"byStatus": {
"pending": { "orders": 1, "ips": 5, "trafficGb": 10.0, "spentUsd": 9.00 },
"paid": { "orders": 0, "ips": 0, "trafficGb": 0, "spentUsd": 0 },
"active": { "orders": 4, "ips": 20, "trafficGb": 100.5, "spentUsd": 68.40 },
"expired": { "orders": 1, "ips": 5, "trafficGb": 10.0, "spentUsd": 9.00 },
"cancelled": { "orders": 1, "ips": 5, "trafficGb": 0, "spentUsd": 3.00 },
"refunded": { "orders": 0, "ips": 0, "trafficGb": 0, "spentUsd": 0 }
},
"byType": {
"private-proxy": {
"orders": 3, "ips": 15, "trafficGb": 0, "spentUsd": 27.00,
"byStatus": {
"active": { "orders": 2, "ips": 10, "trafficGb": 0, "spentUsd": 18.00 },
"expired": { "orders": 1, "ips": 5, "trafficGb": 0, "spentUsd": 9.00 }
}
},
"server-static": {
"orders": 2, "ips": 20, "trafficGb": 20.5, "spentUsd": 36.40,
"byStatus": {
"active": { "orders": 2, "ips": 20, "trafficGb": 20.5, "spentUsd": 36.40 }
}
},
"residential-giga": {
"orders": 2, "ips": 0, "trafficGb": 100.0, "spentUsd": 26.00,
"byStatus": {
"active": { "orders": 1, "ips": 0, "trafficGb": 50.0, "spentUsd": 13.00 },
"pending": { "orders": 1, "ips": 0, "trafficGb": 50.0, "spentUsd": 13.00 }
}
}
}
}Aggregate Object Fields
| Name | Type | Description | Required |
|---|---|---|---|
orders | number | Number of orders | — |
ips | number | Total IPs purchased (count column sum) | — |
trafficGb | number | Total traffic purchased in GB | — |
spentUsd | number | Total amount spent in USD | — |
byStatusalways includes all 6 statuses (zeros for statuses with no orders).byType.*.byStatusonly includes statuses that have at least one order for that type.
List Orders Auth required
GET /orders
List user's orders (paginated).
Query Parameters
| Name | Type | Description | Required |
|---|---|---|---|
page | number | Page number (default: 1) | No |
limit | number | Items per page (default: 20) | No |
type | string | Filter by proxy type ID | No |
Response
{
"orders": [ ... ],
"total": 42,
"page": 1,
"pages": 3
}Get Order by ID Auth required
GET /orders/:id
Get a single order by ID.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Response
{
"id": "80-472109",
"name": "My US Proxies",
"status": "active",
"proxyType": {
"id": "server-static",
"name": "Server Static",
"paymentModel": "postpaid",
"unit": "ip"
},
"count": 5,
"trafficGb": 10,
"billingPeriod": "month",
"proxyUsername": "u_a1b2c3",
"proxyPassword": "xYz9q2Wp4rT8mN1k",
"peer": {
"host": "us1.fonproxy.com",
"portMin": 10000,
"portMax": 19999
},
"countries": { "US": 3, "DE": 2 },
"usage": { "totalGb": 1.2, "maxGb": 10, "usedPercent": 12.0, "remainingGb": 8.8 },
"provisioning": {
"state": "ok",
"assignedCountries": null,
"missingCountries": null,
"assignedCount": 5,
"missingCount": 0
},
"pricing": { "totalUsd": 4.50 },
"dates": { "createdAt": "2026-04-01T10:00:00.000Z", "activatedAt": "2026-04-01T10:00:05.000Z" }
}Order statuses: pending, paid, active, expired, cancelled, refunded
Provisioning Field
Every order response includes a provisioning object that surfaces IP assignment state — the frontend never needs to parse raw meta.
| Name | Type | Description | Required |
|---|---|---|---|
state | "ok" | "pending" | "partial" | Current provisioning state | — |
assignedCountries | Record<string,number> | null | Countries assigned, with count per country | — |
missingCountries | Record<string,number> | null | Countries still missing IPs, with count needed | — |
assignedCount | number | Total IPs successfully assigned | — |
missingCount | number | Total IPs still missing | — |
State meanings:
| State | When | What to show |
|---|---|---|
"ok" | Fully provisioned (or not applicable) | Nothing — order is healthy |
"pending" | Postpaid order paid but waiting for IPs. Not yet running. | Banner: "Your order is being set up…" with missingCountries breakdown |
"partial" | Prepaid order active but some countries had insufficient IPs. Running with partial IPs. | Warning: "Order active with partial IPs" + missingCountries |
Example — pending (postpaid, waiting for DE stock):
{
"state": "pending",
"assignedCountries": { "US": 3, "CA": 2 },
"missingCountries": { "DE": 1 },
"assignedCount": 5,
"missingCount": 1
}Example — partial (prepaid, active but missing some):
{
"state": "partial",
"assignedCountries": { "US": 10 },
"missingCountries": { "FR": 3, "IT": 2 },
"assignedCount": 10,
"missingCount": 5
}The
statefield is enough for the frontend to decide what to render.
Usage Fields
| Name | Type | Description | Required |
|---|---|---|---|
uploadBytes | number | Total bytes uploaded | — |
downloadBytes | number | Total bytes downloaded | — |
totalBytes | number | Upload + download | — |
requestCount | number | Total proxy requests processed | — |
uploadGb | number | Upload in GB (3 decimal places) | — |
downloadGb | number | Download in GB (3 decimal places) | — |
totalGb | number | Total traffic in GB | — |
maxBytes | number | null | Max allowed bytes (giga orders only, null for IP orders) | — |
maxGb | number | null | Max allowed GB (order.trafficGb for giga, null for IP) | — |
remainingBytes | number | null | Remaining bytes before quota is exhausted | — |
remainingGb | number | null | Remaining GB | — |
usedPercent | number | null | Percentage of quota used (0–100, 2 decimal places) | — |
lastReportedAt | string | null | ISO timestamp of last peer usage report | — |
For giga orders (
unit: 'giga'):maxGbequals the purchasedtrafficGb. Frontend can show a progress bar usingusedPercent. For IP orders (unit: 'ip'):maxBytes,maxGb,remainingBytes,remainingGb,usedPercentare allnull— traffic is informational only.
Automatic IP Provisioning
After an order is created and paid, IP assignment happens automatically via an event-driven background process:
| Payment model | Behaviour |
|---|---|
| prepaid | Balance deducted immediately. IPs auto-assigned by country. Order always activated — even if some countries had insufficient IPs (partial). provisioning.state = "partial" with missingCountries breakdown. |
| postpaid | Order stays pending until all IPs assigned. If all countries filled → activates immediately (state: "ok"). If any country is short → stays pending (state: "pending"). Admin fills missing slots; order auto-activates. |
Frontend handling by provisioning.state:
| State | Status | What to show |
|---|---|---|
"ok" | active | Normal order view |
"pending" | pending | Full-page "Setting up your order…" banner with missingCountries table. Order not running. |
"partial" | active | Warning banner "Running with partial IPs — some locations pending" + missingCountries table. IPs are working. |
Admins can use
POST /admin/orders/:id/ips/assign-by-countryorPOST /admin/orders/:id/ips/add-by-countryto fill missing slots. Once all missing slots are filled, the postpaid order auto-activates.
Get Order IPs Auth required
GET /orders/:id/ips
Get the IPs assigned to an order.
- Only active (assigned) and visible IPs are returned — IPs hidden by admin (
visible=false) are excluded. - The
indexfield is the absolute position (0-based). There may be gaps where hidden IPs sit — this is intentional. - Connection info is computed from the active peer:
host : portMin + index. - Credentials are the order's own
proxyUsername/proxyPassword— NOT the original proxy creds.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Response
{
"orderId": "K4Xb9r",
"credentials": {
"username": "user_a1b2c3d4e5f6",
"password": "xYz9q2Wp4rT8mN1k"
},
"ips": [
{
"index": 0,
"host": "us1.fonproxy.com",
"port": 10000,
"originalIp": "185.123.45.67",
"countryCode": { "geoip": "US" },
"city": { "geoip": "New York" },
"state": { "geoip": "NY" },
"status": "active",
"lastVerifiedAt": "2026-03-23T08:14:00.000Z",
"assignedAt": "2026-03-20T10:01:00.000Z"
},
...
],
"total": 2,
"provisioning": {
"state": "ok",
"assignedCountries": null,
"missingCountries": null,
"assignedCount": 2,
"missingCount": 0
}
}Index
1is missing — that IP was hidden by admin. The user sees 0, 2 with ports 10000, 10002.Use
credentials.username/credentials.passwordto authenticate withhost:port.If no peer is active,
hostandportwill benull.For pending orders (
provisioning.state === "pending"),ipswill be empty or partial andprovisioning.missingCountriesshows what's still being allocated. Show a "waiting for IPs" banner.For partial orders (
provisioning.state === "partial"),ipscontains the assigned IPs andprovisioning.missingCountriesshows what's still missing. The order is running — show a warning.
Reset Public Download Hash Auth required
POST /orders/:id/reset-hash
Reset the public download hash for an order.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Response
{ "publicHash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" }Update Order Settings Auth required
PATCH /orders/:id/settings
Update user-editable order settings. All fields optional.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Request Body
| Name | Type | Description | Required |
|---|---|---|---|
name | string | null | Custom label. Pass null to clear. Falls back to order ID in responses. | No |
proxyUsername | string | New proxy username. Letters and digits only. Stored as u_<value>. | No |
proxyPassword | string | New proxy password. Cannot be empty. | No |
{
"name": "My US Proxies",
"proxyUsername": "myuser",
"proxyPassword": "mySecurePass123"
}Response
Full order object (same as GET /orders/:id).
Errors
{ "message": "order.username_empty" }
{ "message": "order.password_empty" }Delete Order Auth required
DELETE /orders/:id
Soft-delete an order. Allowed for all order statuses — pending, paid, active, expired, cancelled, refunded.
Cascades soft-delete to related order_ips, order_usage, and order_renewal_configs. Soft-deleted orders are retained for 30 days before hard-deletion.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Response
{ "deleted": true }Errors
{ "message": "order.not_found" }Order Renewal & Traffic Management
Get Order Pricing Options Auth required
GET /orders/:id/pricing
Get all pricing options for renewal and traffic top-up on an existing order.
Returns:
- Renewal (postpaid only): full price breakdown for renewing the current billing period.
- Traffic top-up (all types): selectable preset options with full breakdown.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Response (postpaid order)
{
"orderId": "80-472109",
"proxyTypeId": "private-proxy",
"proxyTypeName": "Private Proxy",
"paymentModel": "postpaid",
"unit": "ip",
"renewal": {
"billingPeriod": "month",
"breakdown": {
"proxyTypeId": "private-proxy",
"count": 25,
"trafficGb": 50,
"period": "month",
"periodLabel": "Monthly",
"subtotal": 21.38,
"trafficCost": 2.50,
"discount": { "min": 25, "discount": 0.05, "label": "5% off" },
"discountAmount": 1.13,
"total": 23.88
},
"discountPercent": 0,
"discountAmount": 0,
"total": 23.88,
"canRenew": true
},
"trafficTopup": {
"options": [
{
"trafficGb": 5,
"pricePerGb": 0.05,
"subtotal": 0.25,
"discount": null,
"discountAmount": 0,
"total": 0.50
},
...
],
"canTopup": true
}
}Response (prepaid giga order)
{
"orderId": "80-123456",
"proxyTypeId": "residential-giga",
"proxyTypeName": "Residential Giga",
"paymentModel": "prepaid",
"unit": "giga",
"renewal": null,
"trafficTopup": {
"options": [
{
"trafficGb": 1,
"pricePerGb": 1.5,
"subtotal": 1.50,
"discount": null,
"discountAmount": 0,
"total": 1.50
},
...
],
"canTopup": true
}
}
renewalisnullfor prepaid types (no renewal — buy more traffic instead). Traffic top-up options use the same pricing as the initial purchase for that type. Frontend can use presetoptionsor call the custom preview endpoint below.
Preview Traffic Top-up Auth required
GET /orders/:id/traffic-preview
Preview traffic top-up price for a custom GB amount.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Query Parameters
| Name | Type | Description | Required |
|---|---|---|---|
trafficGb | number | GB amount to preview | Yes |
Response
{
"trafficGb": 25,
"pricePerGb": 0.05,
"subtotal": 1.25,
"discount": null,
"discountAmount": 0,
"total": 1.25
}Errors
{ "message": "order.invalid_traffic_gb" }
{ "message": "order.not_found" }Renew Order Auth required
POST /orders/:id/renew
Manually renew a postpaid order for the same billing period. Deducts from balance.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Request Body
No body required — sends a renewal at current pricing.
Response
Full order object.
Errors
{ "message": "order.not_postpaid" }
{ "message": "order.not_renewable" }
{ "message": "order.insufficient_balance" }
{ "message": "order.charge_locked" }Top-up Traffic Auth required
POST /orders/:id/topup-traffic
Add traffic GB to any order (prepaid or postpaid). Deducts from balance.
Pricing uses the same model as purchase:
- Prepaid giga:
pricePerUnit× GB, withtrafficDiscountsapplied. - Postpaid IP:
trafficPricePerGb× GB, withTRAFFIC_VOLUME_DISCOUNTSapplied.
Use GET /orders/:id/pricing or GET /orders/:id/traffic-preview to see exact prices before buying.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Request Body
| Name | Type | Description | Required |
|---|---|---|---|
trafficGb | number | GB to add (must be > 0) | Yes |
{ "trafficGb": 10 }Response
Full order object.
Errors
{ "message": "order.not_active" }
{ "message": "order.insufficient_balance" }
{ "message": "order.charge_locked" }Get Renewal Config Auth required
GET /orders/:id/renewal-config
Get auto-management settings for an order. Returns defaults if never configured.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Response
{
"autoRenew": true,
"autoTopupGb": true,
"autoTopupAmount": 10,
"autoTopupThresholdPercent": 80
}| Name | Type | Description | Required |
|---|---|---|---|
autoRenew | boolean | Postpaid only — auto-renew 24 h before expiry (default: true) | — |
autoTopupGb | boolean | Auto-buy traffic when usage hits threshold (default: true) | — |
autoTopupAmount | number | GB to purchase per auto top-up (default: 10) | — |
autoTopupThresholdPercent | number | Usage % that triggers auto top-up, 1–99 (default: 80) | — |
Update Renewal Config Auth required
PATCH /orders/:id/renewal-config
Update auto-management features. All fields optional — send only what you want to change. Creates the config row with defaults on first call.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:id | string | Order ID | Yes |
Request Body
{
"autoRenew": true,
"autoTopupGb": true,
"autoTopupAmount": 20,
"autoTopupThresholdPercent": 75
}Response
Same shape as GET /orders/:id/renewal-config.
Errors
{ "message": "order.not_postpaid" }
{ "message": "order.invalid_topup_amount" }
{ "message": "order.invalid_topup_threshold" }Renewal Config in Order Responses
Every order object (from GET /orders, GET /orders/:id, etc.) includes a renewalConfig field:
{
"id": "80-472109",
"status": "active",
"renewalConfig": {
"autoRenew": true,
"autoTopupGb": true,
"autoTopupAmount": 10,
"autoTopupThresholdPercent": 80
}
}Auto-Management Summary
| Feature | Trigger | Endpoint to configure |
|---|---|---|
| Auto-renew | ~24 h before expiry (cron) | PATCH /orders/:id/auto-renew |
| Auto traffic top-up | usage ≥ threshold (cron every 10 min) | PATCH /orders/:id/auto-topup |
| Peer-triggered top-up | peer reports exhaustion | internal — POST /internal/peer/traffic/exhausted |
Proxy Download (Public)
List Export Formats
GET /proxy/formats
List available export formats. Public — no auth required.
Response
{
"formats": [
{ "id": "txt", "name": "Plain text — host:port:user:pass", "extension": "txt" },
{ "id": "user-pass-at", "name": "user:pass@host:port", "extension": "txt" },
{ "id": "proto-url", "name": "proto://user:pass@host:port", "extension": "txt" },
{ "id": "curl", "name": "cURL commands", "extension": "sh" },
{ "id": "json", "name": "JSON array", "extension": "json" },
{ "id": "csv", "name": "CSV — host,port,user,pass,proto", "extension": "csv" }
]
}Download Proxy List
GET /proxy/dl/:hash
Download proxy list by public hash. Public — no authentication required.
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
:hash | string | The order's publicHash | Yes |
Query Parameters
| Name | Type | Description | Required |
|---|---|---|---|
format | string | Export format id from /proxy/formats (default: txt) | No |
proto | string | Protocol: http or socks5 (default: http) | No |
Returns a file download with the appropriate content type.
Example txt response:
us1.fonproxy.com:10000:u_a1b2c3:xYz9q2Wp4rT8mN1k
us1.fonproxy.com:10002:u_a1b2c3:xYz9q2Wp4rT8mN1kExample curl response:
curl --proxy u_a1b2c3:xYz9q2Wp4rT8mN1k@us1.fonproxy.com:10000 https://httpbin.org/ip
curl --proxy u_a1b2c3:xYz9q2Wp4rT8mN1k@us1.fonproxy.com:10002 https://httpbin.org/ipExample curl with socks5 (?proto=socks5):
curl --socks5 u_a1b2c3:xYz9q2Wp4rT8mN1k@us1.fonproxy.com:10000 https://httpbin.org/ipThe hash is visible in the order response as
publicHash. Reset it withPOST /orders/:id/reset-hashif compromised.