Order API

FonProxy Order API reference documentation.

FonProxy API — Orders

All /orders/* endpoints require Authorization: Bearer <token> unless noted.


Orders

GET /orders/config

Get the full proxy catalog config. Public — no auth required.

Response (200):

{
  "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" }
  ]
}

POST /orders/preview

Preview order pricing before buying. Requires JWT.

Request body (prepaid giga):

{
  "proxyTypeId": "residential-giga",
  "trafficGb": 50
}

Request body (postpaid IP):

{
  "proxyTypeId": "private-proxy",
  "count": 25,
  "trafficGb": 50,
  "billingPeriod": "month",
  "countries": { "US": 10, "DE": 10, "GB": 5 }
}
FieldTypeRequiredDescription
proxyTypeIdstringyesProxy type ID from config
trafficGbnumbergiga types: yesGB of traffic
countnumberip types: yesNumber of IPs
billingPeriodstringnoweek/month/year
countriesobjectnoCountry → count map

Response (200):

{
  "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
  }
}

POST /orders/create

Create a real order. Both prepaid and postpaid orders deduct balance immediately. Requires JWT.

Request body (postpaid with selected countries):

{
  "proxyTypeId": "private-proxy",
  "count": 25,
  "trafficGb": 50,
  "billingPeriod": "month",
  "countries": { "US": 10, "DE": 10, "GB": 5 }
}

Request body (postpaid with random countries):

{
  "proxyTypeId": "private-proxy",
  "count": 25,
  "trafficGb": 50,
  "billingPeriod": "month"
}

When countries is omitted or null, IPs are assigned from random available countries. Country selection does not affect price.

Response (200): 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

List user's orders (paginated). Requires JWT.

Query params: ?page=1&limit=20&type=residential-giga

Response (200):

{
  "orders": [ ],
  "total": 42,
  "page": 1,
  "pages": 3
}

GET /orders/:id

Get a single order by hashid. Requires JWT.

Response (200):

{
  "id": "k5Xz9qR2Wp",
  "name": "My US Proxies",
  "status": "paid",
  "proxyType": {
    "id": "residential-giga",
    "name": "Residential Giga",
    "paymentModel": "prepaid",
    "unit": "giga"
  },
  "count": 0,
  "trafficGb": 50,
  "billingPeriod": null,
  "proxyUsername": "user_a1b2c3d4e5f6",
  "proxyPassword": "xYz9q2Wp4rT8mN1k",
  "peer": {
    "host": "us1.fonproxy.com",
    "portMin": 10000,
    "portMax": 19999
  },
  "countries": null,
  "usage": {
    "uploadBytes": 0,
    "downloadBytes": 0,
    "totalBytes": 0,
    "requestCount": 0,
    "uploadGb": 0,
    "downloadGb": 0,
    "totalGb": 0,
    "maxBytes": 53687091200,
    "maxGb": 50,
    "remainingBytes": 53687091200,
    "remainingGb": 50,
    "usedPercent": 0,
    "lastReportedAt": null
  },
  "pricing": {
    "pricePerUnit": 1.5,
    "subtotal": 63.75,
    "discountAmount": 11.25,
    "totalUsd": 63.75,
    "totalDisplay": 2637.19,
    "currency": "UAH",
    "exchangeRate": 41.37
  },
  "dates": {
    "createdAt": "2026-03-16T10:00:00.000Z",
    "paidAt": "2026-03-16T10:00:01.000Z"
  },
  "meta": null
}

Order statuses: pending, paid, active, expired, cancelled, refunded

Usage fields

FieldTypeDescription
uploadBytesnumberTotal bytes uploaded
downloadBytesnumberTotal bytes downloaded
totalBytesnumberUpload + download
requestCountnumberTotal proxy requests processed
uploadGbnumberUpload in GB (3 decimal places)
downloadGbnumberDownload in GB (3 decimal places)
totalGbnumberTotal traffic in GB
maxBytesnumber|nullMax allowed bytes (giga orders only, null for IP orders)
maxGbnumber|nullMax allowed GB (= order.trafficGb for giga, null for IP)
remainingBytesnumber|nullRemaining bytes before quota is exhausted
remainingGbnumber|nullRemaining GB
usedPercentnumber|nullPercentage of quota used (0–100, 2 decimal places)
lastReportedAtstring|nullISO timestamp of last peer usage report

For giga orders (unit: 'giga'): maxGb equals the purchased trafficGb. Frontend can show a progress bar using usedPercent. For IP orders (unit: 'ip'): maxBytes, maxGb, remainingBytes, remainingGb, usedPercent are all null — 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 modelBehaviour
prepaidBalance is deducted immediately. IPs are auto-assigned by country. Order is always activated — even if some countries had insufficient IPs (partial). Missing IPs stored in meta.failedCountries. Notification sent on activation.
postpaidOrder is created as pending. IPs are auto-assigned by country. If all countries are fully filled → order activates immediately + notification sent. If any country is short → order stays pending (meta.provisioningPending = true, meta.failedCountries lists what's missing). Admin must manually add the remaining IPs to trigger activation.

meta provisioning fields:

{
  "provisioningPartial": true,
  "assignedCountries": { "US": 3, "CA": 2 },
  "failedCountries": { "DE": 1 }
}

Admins can use POST /admin/orders/:id/ips/assign-by-country or POST /admin/orders/:id/ips/add-by-country to fill missing slots.


GET /orders/:id/ips

Get the IPs assigned to an order. Requires JWT or API key.

  • Only active (assigned) and visible IPs are returned — IPs hidden by admin (visible=false) are excluded.
  • The index field 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.

Response (200):

{
  "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"
    },
    {
      "index": 2,
      "host": "us1.fonproxy.com",
      "port": 10002,
      "originalIp": "91.45.12.33",
      "countryCode": { "geoip": "DE" },
      "city": { "geoip": "Berlin" },
      "state": {},
      "status": "active",
      "lastVerifiedAt": "2026-03-23T08:10:00.000Z",
      "assignedAt": "2026-03-20T10:01:00.000Z"
    }
  ],
  "total": 2
}

Index 1 is missing — that IP was hidden by admin. The user sees 0, 2 with ports 10000, 10002.

Use credentials.username / credentials.password to authenticate with host:port.

If no peer is active, host and port will be null.


POST /orders/:id/reset-hash

Reset the public download hash for an order. Requires JWT.

Response (200):

{ "publicHash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" }

PATCH /orders/:id/settings

Update user-editable order settings. Requires JWT. All fields optional.

FieldTypeDescription
namestring | nullCustom label. Pass null to clear. Falls back to order hashid in responses.
proxyUsernamestringNew proxy username. Only letters and digits. Always stored as u_<value>. Cannot be empty after stripping.
proxyPasswordstringNew proxy password. Cannot be empty.

Body:

{
  "name": "My US Proxies",
  "proxyUsername": "myuser",
  "proxyPassword": "mySecurePass123"
}

Response (200): Full order object (same as GET /orders/:id).

Errors:

{ "message": "order.username_empty" }
{ "message": "order.password_empty" }

Proxy Download (Public — No Auth)

GET /proxy/formats

List available export formats. Public.

{
  "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" }
  ]
}

GET /proxy/dl/:hash?format=txt&proto=http

Download proxy list by public hash. No authentication required.

ParamTypeDefaultDescription
hashstringThe order's publicHash
formatstringtxtExport format id (see /proxy/formats)
protostringhttpProtocol: http or socks5

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:xYz9q2Wp4rT8mN1k

Example 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/ip

Example curl with socks5 (?proto=socks5):

curl --socks5 u_a1b2c3:xYz9q2Wp4rT8mN1k@us1.fonproxy.com:10000 https://httpbin.org/ip

The hash is visible in the order response as publicHash. Reset it with POST /orders/:id/reset-hash if compromised.

Order API — FonProxy