On this page
API reference
Generate proxies and manage your account programmatically over a simple REST API. Base URL https://resifactory.net/api/v1.
Overview
The ResiFactory REST API lets you generate proxy credentials, inspect account balances, and review recent usage — all from your own code, scripts, or CI pipelines.
Base URL
https://resifactory.net/api/v1
All endpoints are served under this path. /api/v1 is the current stable version — it will not receive breaking changes.
HTTPS only
All requests must be made over HTTPS. Plaintext HTTP connections are rejected. Your API key is transmitted in every request header; never send it over an unencrypted channel.
Machine-readable spec
The OpenAPI 3.1 spec is served at:
/api/v1/openapi.jsonImport it into Postman, Insomnia, or any OpenAPI-compatible tool.
All request and response bodies are JSON (Content-Type: application/json). Responses always include a top-level key — either the resource payload or an error object. See Errors for the full error shape.
Authentication
Every request is authenticated with a bearer token. Keys are created in the Developer tab of your dashboard. The full secret is displayed exactly once at creation time — copy it immediately.
Sending the key
Authorization: Bearer rf_live_xxxxxxxxxxxxxxxxxxxx
API keys start with rf_live_. Never share or commit your secret key.
Best practices
One key per application
Create a separate key for each integration so you can revoke one without disrupting others. You can hold up to 10 keys at once.
Minimal scopes
Grant only the scopes the application actually needs. A scraper that only generates proxies does not need usage:read or pools:read.
Rotate & revoke promptly
Rotate keys periodically. If a key is leaked, revoke it immediately from the Developer tab of your dashboard. A revoked key returns 401 unauthorized instantly.
Never commit your API key to source control. Use environment variables or a secret manager (e.g. process.env.RF_API_KEY). If you accidentally push a key, revoke it immediately — git history is public.
Scopes
Each API key carries a set of scopes that control which endpoints it can call. Scopes are set at key creation and cannot be changed afterwards — create a new key if you need different permissions. Keys are created and revoked in the dashboard Developer tab; the API itself cannot mint or revoke keys (a leaked key can never create more). Pick the scopes a key needs at creation time — every key gets proxies:generate, usage:read, and pools:read by default.
| Scope | What it unlocks | Default? |
|---|---|---|
| proxies:generate | Call POST /proxies to generate proxy credentials. |
Yes |
| usage:read | Call GET /balance and GET /usage to read account balances and domain-level usage. |
Yes |
| pools:read | Call GET /pools to list accessible pools and valid targeting options. |
Yes |
GET /me is accessible with any valid key regardless of scopes — it is a lightweight identity check.
Quickstart
Generate your first proxy list in under two minutes.
Create an API key
Log in → Dashboard → Developer. Click New key, give it a name, leave the default scopes, and copy the secret that appears. It will not be shown again.
Generate 10 US proxies
curl -X POST https://resifactory.net/api/v1/proxies \
-H "Authorization: Bearer rf_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"country":"us","quantity":10}'
import requests
res = requests.post(
"https://resifactory.net/api/v1/proxies",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
json={
"country": "us",
"quantity": 10
},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/proxies", {
method: "POST",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({"country":"us","quantity":10})
});
const data = await res.json();
console.log(data);
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
body := []byte(`{"country":"us","quantity":10}`)
req, _ := http.NewRequest("POST", "https://resifactory.net/api/v1/proxies", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Read the response
The default format (list) returns a JSON object where proxies is a newline-delimited string of host:port:user:pass lines:
{
"proxies": "proxy.resifactory.net:8000:user-country-us:pass\n...",
"count": 10,
"country": "us",
"is_demo": false,
"pool": "walmart_premium",
"format": "list"
}
Paste into your tool
Split the string on \n and use each line as a proxy. Each entry is host:port:username:password. Most HTTP clients accept this format directly. Switch to "format":"json" if you prefer a structured array.
Endpoints
All endpoints are relative to https://resifactory.net/api/v1.
/proxies
proxies:generate
Generate a list of proxy credentials. Accounts with zero balance receive non-working demo credentials and is_demo: true in the response.
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
| pool | string | No | Pool ID (e.g. walmart_premium). Omit to use your default pool. Call GET /pools for valid IDs. |
| country | string | No | ISO 3166-1 alpha-2 country code in lowercase (e.g. us, gb, de). |
| quantity | integer | No | Number of proxy lines to return. Range: 1–250,000. Default: 5,000. |
| proxyType | string | No | rotating | sticky | mobile_rotating | mobile_sticky. Default: rotating. |
| sessionDuration | integer | No | Sticky session lifetime in minutes. Range: 1–1,440 (24 h). Ignored for rotating types. |
| state | string | No | US state targeting (US only). Use the state abbreviation (e.g. CA, TX). Call GET /pools for valid values. |
| format | string | No | list | json | userpass. Default: list. See Output Formats. |
Example request
curl -X POST https://resifactory.net/api/v1/proxies \
-H "Authorization: Bearer rf_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"country":"us","quantity":100,"proxyType":"sticky","sessionDuration":30,"format":"json"}'
import requests
res = requests.post(
"https://resifactory.net/api/v1/proxies",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
json={
"country": "us",
"quantity": 100,
"proxyType": "sticky",
"sessionDuration": 30,
"format": "json"
},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/proxies", {
method: "POST",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({"country":"us","quantity":100,"proxyType":"sticky","sessionDuration":30,"format":"json"})
});
const data = await res.json();
console.log(data);
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
body := []byte(`{"country":"us","quantity":100,"proxyType":"sticky","sessionDuration":30,"format":"json"}`)
req, _ := http.NewRequest("POST", "https://resifactory.net/api/v1/proxies", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Success response (200)
{
"proxies": [
{ "host": "proxy.resifactory.net", "port": 8000, "username": "user-country-us-session-abc123", "password": "secret" },
...
],
"count": 100,
"country": "us",
"is_demo": false,
"pool": "walmart_premium",
"format": "json"
}
Relevant errors: unauthorized (401), insufficient_scope (403), account_disabled (403), pool_not_available (404), invalid_request (400), rate_limited (429).
/balance
usage:read
Returns the remaining GB balance for each pool the caller can access. Balances are floats rounded to four decimal places.
Example request
curl -X GET https://resifactory.net/api/v1/balance \
-H "Authorization: Bearer rf_live_YOUR_KEY"
import requests
res = requests.get(
"https://resifactory.net/api/v1/balance",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/balance", {
method: "GET",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY"
}
});
const data = await res.json();
console.log(data);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://resifactory.net/api/v1/balance", nil)
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Success response (200)
{
"balances": {
"walmart_premium": 12.5342,
"retail_select": 6.0000
}
}
Relevant errors: unauthorized (401), insufficient_scope (403), rate_limited (429).
/usage
usage:read
Returns top domains by bandwidth usage for this account. Useful for auditing traffic distribution across targets.
Query parameters
| Parameter | Type | Notes |
|---|---|---|
| limit | integer | Number of domains to return. Range: 1–100. Default: 25. |
Example request
curl -X GET https://resifactory.net/api/v1/usage?limit=5 \
-H "Authorization: Bearer rf_live_YOUR_KEY"
import requests
res = requests.get(
"https://resifactory.net/api/v1/usage?limit=5",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/usage?limit=5", {
method: "GET",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY"
}
});
const data = await res.json();
console.log(data);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://resifactory.net/api/v1/usage?limit=5", nil)
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Success response (200)
{
"top_domains": [
{ "domain": "walmart.com", "bytes": 1073741824, "requests": 45023 },
{ "domain": "target.com", "bytes": 536870912, "requests": 22100 }
]
}
bytes is a raw integer (not formatted). Divide by 1,073,741,824 to convert to GB.
/pools
pools:read
Returns all pools accessible by this API key, plus valid targeting values (countries, US states, session duration range, carriers). Always call this endpoint at startup to discover valid pool IDs and targeting options — do not hardcode them.
Example request
curl -X GET https://resifactory.net/api/v1/pools \
-H "Authorization: Bearer rf_live_YOUR_KEY"
import requests
res = requests.get(
"https://resifactory.net/api/v1/pools",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/pools", {
method: "GET",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY"
}
});
const data = await res.json();
console.log(data);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://resifactory.net/api/v1/pools", nil)
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Success response (200)
{
"pools": [
{
"id": "walmart_premium",
"label": "Premium",
"host": "proxy.resifactory.net",
"proxy_types": ["rotating", "sticky"]
}
],
"targeting": {
"countries": ["us", "gb", "de", "ca", "jp", "au"],
"us_states": ["CA", "TX", "NY", "FL", "WA"],
"session_minutes": { "min": 1, "max": 1440 },
"carriers": []
}
}
/me
any valid key
Returns account identity and a summary of the current key. Use this as a lightweight health check to verify that a key is valid and to inspect its scopes at runtime.
Example request
curl -X GET https://resifactory.net/api/v1/me \
-H "Authorization: Bearer rf_live_YOUR_KEY"
import requests
res = requests.get(
"https://resifactory.net/api/v1/me",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/me", {
method: "GET",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY"
}
});
const data = await res.json();
console.log(data);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://resifactory.net/api/v1/me", nil)
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Success response (200)
{
"id": 42,
"username": "jane_doe",
"scopes": ["proxies:generate", "usage:read", "pools:read"],
"balances": { "walmart_premium": 12.5342 },
"key": {
"name": "My scraper",
"prefix": "rf_live_",
"last4": "x7kQ"
}
}
Pools & Targeting
ResiFactory operates multiple proxy pools. Each pool has its own host, balance, and set of supported proxy types. Call GET /pools at startup to discover what is currently accessible — never hardcode pool IDs or targeting values.
Proxy types
- rotating — New IP on every request. Ideal for large-scale scraping.
- sticky — Same IP for a configurable session window (1–1,440 min).
- mobile_rotating — Mobile carrier IPs, rotates per request.
- mobile_sticky — Mobile carrier IPs with a session window.
Session duration
Applies only to sticky and mobile_sticky types. Value is in minutes.
- Minimum: 1 minute
- Maximum: 1,440 minutes (24 hours)
- Default (if omitted): pool default
Rotating types silently ignore this field.
Geographic targeting
- country — ISO 3166-1 alpha-2 (e.g.
us,gb) - state — US only; pass two-letter abbreviation (e.g.
CA,NY)
Valid values are returned dynamically by GET /pools.
Tip: State targeting (state) is only valid when country is us. Attempting invalid combinations returns 400 invalid_request.
Output Formats
The format field on POST /proxies controls how credentials are serialized in the response. Choose the format that best matches your tool's import requirements.
list (default)
proxies is a single newline-delimited string. Each line is host:port:username:password. Split on \n and filter empty strings.
"proxies": "proxy.resifactory.net:8000:user-s-abc:pass\nproxy.resifactory.net:8000:user-s-xyz:pass"
json
proxies is a JSON array of objects. Easiest to work with programmatically — each entry has host, port, username, and password fields.
"proxies": [
{
"host": "proxy.resifactory.net",
"port": 8000,
"username": "user-s-abc",
"password": "pass"
}
]
userpass
proxies is a newline-delimited string in user:pass@host:port format. Useful for tools that expect credentials before the host.
"proxies": "user-s-abc:[email protected]:8000\nuser-s-xyz:[email protected]:8000"
Rate Limits
Rate limits are enforced per API key over a rolling 60-second window. Different endpoint groups have separate limits so heavy proxy generation does not starve read endpoints.
| Endpoint group | Endpoints | Limit |
|---|---|---|
| Generate | POST /proxies |
20 / min |
| Reads | GET /balance, GET /usage, GET /pools, GET /me |
120 / min |
Response headers
Every response includes rate-limit headers so you can track consumption without hitting the limit:
| Header | Description |
|---|---|
| RateLimit-Limit | Maximum requests allowed in the current window. |
| RateLimit-Remaining | Requests remaining before you hit the limit. |
| RateLimit-Reset | Unix timestamp at which the window resets. |
| Retry-After | Seconds to wait before retrying. Present only on 429 responses. |
Handling 429
When you receive a 429 rate_limited response, read the Retry-After header and wait that many seconds before retrying. Use exponential backoff with jitter for burst scenarios:
// Pseudo-code: exponential backoff with jitter
async function callWithBackoff(fn, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const result = await fn();
if (result.status !== 429) return result;
const retryAfter = parseInt(result.headers['retry-after'] || '1', 10);
const jitter = Math.random() * 1000;
await sleep((retryAfter * 1000 + jitter) * Math.pow(2, attempt));
}
throw new Error('Max retry attempts exceeded');
}
Errors
All error responses share a common envelope:
{
"error": {
"code": "insufficient_scope",
"message": "This key does not have the proxies:generate scope."
}
}
| Error code | HTTP status | Meaning | How to resolve |
|---|---|---|---|
| unauthorized | 401 | Missing, malformed, or revoked API key. | Verify the Authorization header is present and starts with Bearer rf_live_. Regenerate the key if it was revoked. |
| account_disabled | 403 | Your account has been suspended. | Contact support via Discord to investigate. |
| insufficient_scope | 403 | The key lacks the required scope for this endpoint. | Create a new key with the required scope (scopes cannot be changed after creation). |
| pool_not_available | 404 | The specified pool ID does not exist or is not accessible by this key. | Call GET /pools to get the current list of accessible pool IDs. |
| invalid_request | 400 | A request parameter is missing, out of range, or an invalid combination was provided. | Read the message field for the specific issue. Check parameter types, ranges, and targeting constraints. |
| rate_limited | 429 | You have exceeded the rate limit for this endpoint group. | Wait the number of seconds in the Retry-After header, then retry with exponential backoff. |
| internal_error | 500 | An unexpected server-side error occurred. | Retry after a short delay. If the error persists, report it in our Discord support channel. |
Code Examples
The two most common operations — generating proxies and reading balance. Switch the language tab to match your stack.
Generate proxies
curl -X POST https://resifactory.net/api/v1/proxies \
-H "Authorization: Bearer rf_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"country":"us","quantity":50,"proxyType":"rotating","format":"json"}'
import requests
res = requests.post(
"https://resifactory.net/api/v1/proxies",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
json={
"country": "us",
"quantity": 50,
"proxyType": "rotating",
"format": "json"
},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/proxies", {
method: "POST",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({"country":"us","quantity":50,"proxyType":"rotating","format":"json"})
});
const data = await res.json();
console.log(data);
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
body := []byte(`{"country":"us","quantity":50,"proxyType":"rotating","format":"json"}`)
req, _ := http.NewRequest("POST", "https://resifactory.net/api/v1/proxies", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Read balance
curl -X GET https://resifactory.net/api/v1/balance \
-H "Authorization: Bearer rf_live_YOUR_KEY"
import requests
res = requests.get(
"https://resifactory.net/api/v1/balance",
headers={"Authorization": "Bearer rf_live_YOUR_KEY"},
)
print(res.json())
const res = await fetch("https://resifactory.net/api/v1/balance", {
method: "GET",
headers: {
Authorization: "Bearer rf_live_YOUR_KEY"
}
});
const data = await res.json();
console.log(data);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://resifactory.net/api/v1/balance", nil)
req.Header.Set("Authorization", "Bearer rf_live_YOUR_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
Versioning & Changelog
The current API version is v1, served at /api/v1/. This path is frozen — no breaking changes will ever be made under /api/v1. A breaking change (removed field, changed semantics, altered authentication) will always ship under a new version path (e.g. /api/v2) with advance notice and a deprecation period.
What counts as breaking
- Removing or renaming a response field
- Changing a field's type
- Changing an error code or HTTP status
- Removing an endpoint
- Requiring a new mandatory parameter
What is not breaking
- Adding new optional response fields
- Adding new optional request parameters
- Adding new endpoints
- Adding new error codes for new conditions
Changelog
Initial release · June 17, 2026
Five endpoints — POST /proxies (generation), GET /balance + GET /usage (usage reads), GET /pools (pool discovery), and GET /me (identity) — across three scopes: proxies:generate, usage:read, pools:read. API keys are created and revoked only from the dashboard Developer tab — the API never manages keys, so a leaked key can never mint another. Request examples ship in cURL, Python, JavaScript, and Go, alongside a machine-readable OpenAPI 3.1 spec.
FAQ
I called POST /proxies but got demo credentials back. What happened?
Your account balance is zero for the requested pool. The API returns non-working demo credentials and sets is_demo: true in the response rather than returning an error. Check the balance with GET /balance and top up from the dashboard. Once you have balance, subsequent calls return live credentials.
I'm getting 401 unauthorized even though I copied the key correctly.
The most common causes are: (1) the key was revoked from the dashboard; (2) there is whitespace or a newline appended to the key string; (3) the Authorization header value is missing the Bearer prefix (note the trailing space). Call GET /me as a quick sanity check — if it returns 401, the key is definitively invalid.
I'm getting 403 insufficient_scope. How do I fix it?
Scopes are baked into a key at creation time and cannot be changed. Create a new key with the required scope in the dashboard Developer tab. Check what scopes a key has using GET /me.
How do I handle 429 rate limit errors in production?
Read the Retry-After header from the 429 response — it tells you the exact number of seconds to wait. Implement exponential backoff with jitter: after the first 429, wait Retry-After seconds; after the second, wait double; and so on. For the proxy-generation endpoint (20/min limit), batching all required proxies into a single large POST /proxies call (up to 250,000 lines) is more efficient than many small calls.
My balance dropped unexpectedly. Can I see where the GB went?
Call GET /usage?limit=100 to see the top domains by byte consumption for your account. This gives a per-domain breakdown of traffic. If usage looks unexpected, check whether your proxy credentials have been shared or leaked — if so, revoke the affected key immediately and generate fresh credentials.
Do sticky sessions actually hold the same IP for the full duration?
Yes, for the duration you specify (1–1,440 minutes). Each unique username in the returned credential list corresponds to a pinned session. Requests made with the same username:password pair will exit from the same IP until the session expires. Once a session expires, the next request on that credential rotates to a new IP automatically.
Can I have more than 10 API keys?
No — the limit is 10 keys per account. If you hit the limit, delete unused keys from the dashboard Developer tab to free slots. If you have a legitimate need for more keys (e.g. many separate services), contact support via Discord.
Questions? Reach us on Discord, or grab the machine-readable OpenAPI spec.