Developers

API reference

Generate proxies and manage your account programmatically over a simple REST API. Base URL https://resifactory.net/api/v1.

OpenAPI 3.1 spec
On this page
  1. Getting started
  2. Overview
  3. Authentication
  4. Scopes
  5. Quickstart
  6. Endpoints
  7. GET/me
  8. POST/proxies
  9. GET/balance
  10. GET/usage
  11. GET/pools
  12. GET/pools/{id}
  13. GET/status
  14. GET/usage/daily
  15. GET/usage/domains
  16. GET/transactions
  17. GET/generations
  18. POST/balance/convert
  19. POST/billing/topups
  20. GET/billing/topups/{id}
  21. GET/isp/orders
  22. GET/isp/orders/{id}/ips
  23. POST/isp/orders/{id}/reset-password
  24. Reference
  25. Pools & targeting
  26. Output formats
  27. Strict mode
  28. Idempotency
  29. Pagination
  30. Errors
  31. Rate limits
  32. Code examples
  33. Versioning
  34. FAQ
BreakingTwo breaking changes, 2026-08-06 and 2026-08-31
Breaking, 2026-08-06: balances on GET /balance/GET /me changed from a plain number map to { gb, price_per_gb, currency } objects — read balances[poolId].gb, or keep reading balances_gb until its Sunset if you can't migrate yet. Also, the carrier body parameter and the socks5 protocol value were removed from POST /proxies: carrier targeting was withdrawn from the platform on 2026-07-22 with the parameter silently ignored ever since, and no relay had ever run a socks5 listener, so a line generated with either could never actually connect. socks5 returned on 2026-10-23 on port 1080, on every pool except Essential. https was removed the same day for an unrelated reason (two relays couldn't yet serve :7443) and RESTORED 2026-08-06 by RF-16 once every relay served it again, then withdrawn on 2026-08-31 (below). Removing carrier and socks5 corrects the published contract; no working integration loses anything beyond those two. Full detail in Versioning & Changelog.
Breaking, 2026-08-31: the https protocol value has been withdrawn from POST /proxies. http (port 7777) is served on every pool, and socks5 (port 1080) on every pool except Essential. https is not offered. We restored https on 2026-08-06 after verifying it across the whole fleet, and that verification was accurate at the time — but our relay fleet was rebuilt on new hardware on 2026-08-18, and the rebuilt machines were never given the TLS certificate or the firewall rule the endpoint needs. :7443 has therefore been unreachable since that date, and any proxy line generated with https in that window could not connect. Rather than restore an endpoint we cannot yet renew certificates for automatically, we have withdrawn it. What to do: send protocol: "http" (or "socks5" on any pool except Essential), or omit the field entirely — it already defaults to http. If you omit it, or you use our default lenient validation, nothing changes for you. If you call the API in strict mode and still send protocol: "https", you will now receive a 422 with not_in_enum instead of a 200 — that is the one case that breaks, and it is the same shape as the socks5 removal above. We are sorry for the 13 days during which this page said https worked and it did not.

Overview

Start here

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, meant to stay non-breaking — see Versioning & Changelog for the one documented exception and how it's mitigated.

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.json

Import 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

Bearer tokens

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

Set once, per key

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.

ScopeWhat it unlocks
proxies:generate Generate proxy credentials. The only scope that can spend bandwidth.
usage:read Read balances, usage, transactions, and generation history.
pools:read Read the pools your account can use and their targeting options.
billing:write Move money on the account. The only scope that can spend dollars — must be requested explicitly and carries a monthly spend cap.
isp:read List your ISP proxy orders and their subnets, and download their IPs with logins. Opt-in: every reveal is recorded on your account.
isp:write Reset the proxy password of an ISP order. Opt-in.
entitlements:read Check whether an account holds a live product licence. First-party only — issued to a ResiFactory control plane by an operator, never selectable when you create your own key.
entitlements:claim Register a machine against an account's licence seats when checking it. First-party only — issued to a ResiFactory control plane by an operator, never selectable when you create your own key.

GET /me and GET /status are accessible with any valid key regardless of scopes — lightweight identity and health checks.

Quickstart

Under two minutes

Generate your first proxy list in under two minutes.

Step 1

Create an API key

Log in → Dashboard → Developer. Click Create key, give it a name, leave the default scopes, and copy the secret that appears. It will not be shown again.

Step 2

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 '{"pool":"mobile_dedicated","country":"us","quantity":10}'
Step 3

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": "mobile.resifactory.net:7777:ResiFactory_a3f9c142~us~1a2b3c4d~0~~md:pass\n...",
  "count": 10,
  "country": "US",
  "is_demo": false,
  "pool": "mobile_dedicated",
  "format": "list"
}
Step 4

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.

Good to know

Supported destination ports

Proxied CONNECT requests are supported on ports 80 and 443. Requests to other destination ports are refused and return a generic 502.

In practice this affects background traffic rather than the work you configure — most of it is WebRTC/STUN (3478) and mobile push (5228) emitted automatically by browsers and operating systems using the same proxy settings. If you are deliberately proxying a protocol on another port, it will not work over http: use socks5 (port 1080, any TCP destination port, every pool except Essential), or reach out through support before building around it.

Endpoints

Relative to /api/v1

All endpoints are relative to https://resifactory.net/api/v1. Every parameter table below is generated directly from the same manifest that builds the OpenAPI spec — the two can never drift apart.

GET /me any valid key

Account summary

Returns your account id, username, the scopes on this key, your balances, and this key's metadata. Works with any valid key regardless of scopes — useful as a credential health check.

Errors: invalid_params (422), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/me \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
POST /proxies proxies:generate

Generate proxies

Returns formatted proxy lines. A zero balance or a coming-soon pool returns non-working demo credentials so an integration can be wired up before purchase. Send an Idempotency-Key header so a retried request cannot mint a second batch.

Accepts an Idempotency-Key header — see Idempotency.

Request body (JSON)

FieldTypeRequiredDefaultNotes
pool string No — Pool id to generate from (e.g. `mobile_dedicated`). Omit to use the default for your `proxyType`: `mobile_dedicated` for `mobile_rotating`/`mobile_sticky`, otherwise the Premium pool. Call GET /pools to see the pool ids available to your account.
country string No — ISO 3166-1 alpha-2 country code to target (e.g. `us`, `gb`), case-insensitive. Omitted resolves to `us`. An unrecognized value is refused with 422 `invalid_country`, and a supported code this pool cannot serve with 422 `country_not_available` — it is never silently replaced. Call GET /pools to see which countries a given pool supports.
quantity integer No 5000 Number of proxy lines to generate in this batch.
proxyType string No rotating `rotating` assigns a new exit IP per request. `sticky` holds one exit IP for `sessionDuration` minutes. The `mobile_*` variants are the same behavior on mobile exit IPs.
sessionDuration integer No — Minutes to hold the same exit IP for `sticky` or `mobile_sticky`; defaults to 60 when omitted. Ignored (treated as 0) for `rotating` and `mobile_rotating`. On Essential a session holds at most 120 minutes, whatever value is sent.
state string No — US state to target, as the full lowercase state name (e.g. `california`), not a postal abbreviation. Only applies when the resolved country is `us`; an unrecognized value is silently dropped — no state targeting is applied, and no error is returned even in strict mode.
protocol string No http Proxy protocol for generated entries: `http` (port 7777) or `socks5` (port 1080, any TCP destination port). Essential is HTTP only: a `socks5` request on Essential returns `http` lines. In the default lenient mode any other value falls back to `http`; in strict mode it is rejected with `not_in_enum`.
asn string No — Optional ASN targeting. Accepts `AS7922` or `7922`; targets one network. Not applied on geo-forced pools. A valid ASN with no available exit fails the request upstream rather than silently falling back.
format string No list Shape of `proxies` in the response: `list` is newline-delimited `host:port:user:pass`, `json` is an array of proxy objects, `userpass` is newline-delimited `user:pass@host:port`.

Errors: country_not_available (422), idempotency_key_reuse (409), insufficient_scope (403), invalid_country (422), invalid_params (422), pool_not_available (404), rate_limited (429), unauthorized (401) — see Errors.

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"}'
GET /balance usage:read

Balances per accessible pool

Remaining bandwidth for every pool your account can use, with the current price per GB so you can compute spend without hardcoding it.

Errors: insufficient_scope (403), invalid_params (422), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/balance \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /usage usage:read Deprecated

Top domains (deprecated)

Deprecated alias of GET /usage/domains, kept for existing integrations. Use /usage/domains, which supports date ranges and pagination.

Query parameters

ParameterTypeRequiredDefaultNotes
limit integer No 25 Maximum number of domains to return, ordered by bytes transferred descending.

Errors: insufficient_scope (403), invalid_params (422), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/usage?limit=5 \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /pools pools:read

Accessible pools and their targeting options

Lists the pools your account can use. Each pool carries its own targeting options — country lists differ per pool, so discover them here rather than hardcoding.

Errors: insufficient_scope (403), invalid_params (422), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/pools \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /pools/{id} pools:read

One pool

Returns a single pool your account can use. A pool id your account cannot use returns the same 404 as an id that does not exist.

Path parameters

NameNotes
id Pool id, as returned by GET /pools.

Errors: insufficient_scope (403), invalid_params (422), pool_not_available (404), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/pools/123 \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /status any valid key

Service status and this key's budgets

Reports service availability, the API behaviour version, the scopes and per-minute rate budgets on this key, and any active deprecations.

Errors: invalid_params (422), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/status \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /usage/daily usage:read

Daily bandwidth

Bandwidth and request counts per day per pool. Omit the date range for the last 30 days.

Query parameters

ParameterTypeRequiredDefaultNotes
from string (date) No — Start of the date range (YYYY-MM-DD), inclusive. Defaults to 30 days before today when omitted, independent of `to`.
to string (date) No — End of the date range (YYYY-MM-DD), inclusive. Defaults to today when omitted, independent of `from`.
pool string No — Restrict to one pool id, as returned by GET /pools. Omit (or send `all`) to combine every pool your account can access. A pool id you cannot use returns the same 404 as one that does not exist.

Errors: insufficient_scope (403), invalid_params (422), pool_not_available (404), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/usage/daily \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /usage/domains usage:read

Bandwidth by destination domain

Your top destination domains by bytes, cursor-paginated. Replaces GET /usage, which returned an unbounded top-25 with no date range. Per-domain history covers the last 90 days; a range reaching past that returns 422 `invalid_params`.

Cursor-paginated — see Pagination.

Query parameters

ParameterTypeRequiredDefaultNotes
from string (date) No — Start of the date range (YYYY-MM-DD), inclusive. Defaults to 30 days before today when omitted, independent of `to`. Must be within the last 90 days (today counts as one).
to string (date) No — End of the date range (YYYY-MM-DD), inclusive. Defaults to today when omitted, independent of `from`. The range may span at most 90 days.
pool string No — Restrict to one pool id, as returned by GET /pools. Omit (or send `all`) to combine every pool your account can access. A pool id you cannot use returns the same 404 as one that does not exist.
limit integer No 25 Maximum number of domains to return on this page.
cursor string (cursor) No — Opaque pagination cursor from a previous response's `next_cursor`. Omit to fetch the first page.

Errors: insufficient_scope (403), invalid_params (422), pool_not_available (404), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/usage/domains?limit=5 \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /transactions usage:read

Account transactions

Purchases, credits, and conversions, newest first, cursor-paginated.

Cursor-paginated — see Pagination.

Query parameters

ParameterTypeRequiredDefaultNotes
from string (date) No — Start of the date range (YYYY-MM-DD), inclusive. Unlike GET /usage/daily and GET /usage/domains, an omitted `from` is not defaulted to 30 days ago — it leaves the range unbounded below.
to string (date) No — End of the date range (YYYY-MM-DD), inclusive. An omitted `to` leaves the range unbounded above, not defaulted to today.
pool string No — Restrict to one pool id, as returned by GET /pools. Omit (or send `all`) to combine every pool your account can access. A pool id you cannot use returns the same 404 as one that does not exist.
limit integer No 25 Maximum number of transactions to return on this page.
cursor string (cursor) No — Opaque pagination cursor from a previous response's `next_cursor`. Omit to fetch the first page.

Errors: insufficient_scope (403), invalid_params (422), pool_not_available (404), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/transactions \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /generations usage:read

Generation history

Every proxy generation on this account, newest first, cursor-paginated.

Cursor-paginated — see Pagination.

Query parameters

ParameterTypeRequiredDefaultNotes
limit integer No 25 Maximum number of generation events to return on this page.
cursor string (cursor) No — Opaque pagination cursor from a previous response's `next_cursor`. Omit to fetch the first page.

Errors: insufficient_scope (403), invalid_params (422), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/generations \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
POST /balance/convert billing:write

Convert balance between pools

Moves leftover bandwidth from one pool to another at current prices. Value-preserving and rounded down, so a conversion never creates value. No card is charged.

Accepts an Idempotency-Key header — see Idempotency.

Request body (JSON)

FieldTypeRequiredDefaultNotes
from_pool string Yes — Pool id to move balance out of, as returned by GET /pools (e.g. `mobile_dedicated`). A pool id you cannot use returns the same 404 as one that does not exist.
to_pool string Yes — Pool id to move balance into, as returned by GET /pools. Must be a different pool than `from_pool` with a distinct balance — the two cannot share one wallet, refused with `invalid_request` if they do. A pool id you cannot use returns the same 404 as one that does not exist.
gb number Yes — Amount to move out of `from_pool`, in GB (between 0.1 and 100000). The destination amount is computed server-side from live per-GB prices and rounded down — the destination pool is never credited more dollar value than `from_pool` loses. There is no destination-amount parameter; do not compute or send one.

Errors: idempotency_key_reuse (409), insufficient_scope (403), internal_error (500), invalid_params (422), invalid_request (400), pool_not_available (404), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X POST https://resifactory.net/api/v1/balance/convert \
  -H "Authorization: Bearer rf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from_pool":"mobile_dedicated","to_pool":"retail_select","gb":0.1}'
POST /billing/topups billing:write

Start a bandwidth top-up

Starts a purchase. By default returns a hosted checkout URL to open or forward; balance is credited when payment completes, so the top-up is always created as pending. Requires a key with a monthly spend cap. Idempotency-Key is required.

Accepts an Idempotency-Key header — required — see Idempotency.

Request body (JSON)

FieldTypeRequiredDefaultNotes
pool string Yes — Pool id to top up, as returned by GET /pools. A pool id you cannot use returns the same 404 as one that does not exist.
gb number Yes — Amount to purchase, in GB. The charge is computed server-side from the pool's live price per GB — there is no amount parameter; do not compute or send one. Rejected with invalid_params if the resulting charge is below the $0.50 minimum.
payment_method string No checkout `checkout` (default) returns a hosted Stripe Checkout URL — works for every account. `saved_card` charges a card retained by a prior on-session purchase off-session; today this requires AutoBuy to be enabled on the account, which is not yet generally available, so it returns `card_not_available` for effectively every account. Use `checkout` unless you have confirmed otherwise via GET /me.
discount_code string No — Reserved for a future release — discount codes are not supported on this endpoint yet. The field stays part of the contract so a client already sending one is not broken once support ships, but a non-null value is rejected with invalid_params rather than silently accepted and ignored; omit it.

Errors: card_not_available (409), idempotency_key_reuse (409), insufficient_scope (403), internal_error (500), invalid_params (422), invalid_request (400), payment_failed (402), pool_not_available (404), rate_limited (429), spend_cap_exceeded (402), unauthorized (401) — see Errors.

Example request

curl -X POST https://resifactory.net/api/v1/billing/topups \
  -H "Authorization: Bearer rf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pool":"mobile_dedicated","gb":0.1}'
GET /billing/topups/{id} billing:write

Top-up status

Poll a top-up until it reaches paid or failed. Uses the read rate budget, not the billing one, so polling never blocks a retry.

Path parameters

NameNotes
id Top-up id, as returned by POST /billing/topups.

Errors: insufficient_scope (403), invalid_params (422), not_found (404), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/billing/topups/123 \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /isp/orders isp:read

Your ISP orders and subnets

Lists your ISP proxy orders, newest first, with the /24 subnets each one holds. Carries no logins: use GET /isp/orders/{id}/ips for those.

Errors: insufficient_scope (403), invalid_params (422), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/isp/orders \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
GET /isp/orders/{id}/ips isp:read

IPs of an ISP order, with logins

Every IP of one order as an endpoint line `host:port:username:password`. One login serves every IP of an order. `format=csv` and `format=txt` return a file download instead of JSON. Each call is a credential reveal and is recorded on your account, exactly as the dashboard's Show credentials is.

Path parameters

NameNotes
id Order id, as returned by GET /isp/orders.

Query parameters

ParameterTypeRequiredDefaultNotes
format string No json `json` (default); `csv` with columns host, port, username, password, line; or `txt`, one endpoint line per row.

Errors: insufficient_scope (403), internal_error (500), invalid_params (422), not_found (404), order_not_active (409), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X GET https://resifactory.net/api/v1/isp/orders/123/ips \
  -H "Authorization: Bearer rf_live_YOUR_KEY"
POST /isp/orders/{id}/reset-password isp:write

Reset an ISP order's password

Sets a new password on the order's login; the username stays. The proxies accept the new password within about a minute, and the old one stops working. Fetch GET /isp/orders/{id}/ips to read it; it is also emailed to your account address.

Accepts an Idempotency-Key header — see Idempotency.

Path parameters

NameNotes
id Order id, as returned by GET /isp/orders.

Errors: idempotency_key_reuse (409), insufficient_scope (403), invalid_params (422), not_found (404), order_not_active (409), rate_limited (429), unauthorized (401) — see Errors.

Example request

curl -X POST https://resifactory.net/api/v1/isp/orders/123/reset-password \
  -H "Authorization: Bearer rf_live_YOUR_KEY"

Pools & Targeting

Discover, never hardcode

ResiFactory operates multiple proxy pools. Each pool has its own host, balance, and set of supported proxy types and countries. Call GET /pools at startup to discover what is currently accessible to your account — never hardcode pool IDs or targeting values, and never assume one pool's country list applies to another.

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; up to 120 min on Essential).
  • mobile_rotating — Mobile exit IPs, rotates per request.
  • mobile_sticky — Mobile exit 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); on Essential a session holds at most 120 minutes
  • Default (if omitted): pool default

Rotating types silently ignore this field.

Geographic targeting

  • country — ISO 3166-1 alpha-2, lowercase (e.g. us, gb)
  • state — US only; the full lowercase state name (e.g. california), not a postal abbreviation

Country and state lists are set per pool, not globally — read each pool's own targeting.countries / targeting.us_states from GET /pools rather than assuming every pool supports the same list.

Tip: State targeting (state) only applies when country resolves to us. An unrecognized state is silently dropped, not an error — even in strict mode.

Output Formats

Match your tool

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": "premium.resifactory.net:7777:ResiFactory_a3f9c142~us~1a2b3c4d:pass\npremium.resifactory.net:7777:ResiFactory_a3f9c142~us~9f8e7d6c: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": "premium.resifactory.net",
    "port": 7777,
    "username": "ResiFactory_a3f9c142~us~1a2b3c4d",
    "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": "ResiFactory_a3f9c142~us~1a2b3c4d:[email protected]:7777\nResiFactory_a3f9c142~us~9f8e7d6c:[email protected]:7777"

Strict Mode

RF-Strict: 1

By default the API is lenient: an invalid or out-of-range parameter is coerced to its default or nearest valid value rather than failing the request. Send RF-Strict: 1 to opt out of that and have invalid parameters rejected instead.

Lenient (default)

An invalid parameter is coerced and the request still succeeds. The response adds a Warning header naming the coerced field(s), plus Deprecation: true and a Sunset date — silent coercion is being phased out. The exact sunset date is also in GET /status's deprecations array.

Strict

With RF-Strict: 1, the same problem is a 422 invalid_params instead, with a details array naming each rejected field, why, and what was sent:

{
  "error": {
    "code": "invalid_params",
    "message": "One or more parameters are invalid.",
    "details": [
      { "field": "quantity", "reason": "above_maximum", "got": 900000 }
    ]
  }
}

RF-API-Version

Every response — success or error — carries an RF-API-Version header: the date-stamped behaviour version it was generated under. Pin your integration's expectations against it.

Strict is becoming the default. New integrations should send RF-Strict: 1 on every request — silent coercion exists for callers who predate it, not as the recommended behavior.

Idempotency

Idempotency-Key header

Send an Idempotency-Key header — any string you generate, unique per logical operation — on a supporting endpoint so a retried request (timeout, dropped connection, anything) cannot double-apply. Replaying the same key with the same request body returns the original response again, unchanged, with an Idempotency-Replayed: true header. Replaying the same key with a different body is refused with 409 idempotency_key_reuse — that combination means the key was reused for a different operation, not retried. The same applies to the same key sent to a different order's URL.

Scope

A key is scoped to your API key and the specific endpoint — the same Idempotency-Key string is safe to reuse for two different operations on two different endpoints.

What gets cached

Only a successful (2xx) response is cached against the key. A failed attempt leaves the key retryable — fix the request and send it again with the same key.

Where it applies

  • POST /proxies
  • POST /balance/convert
  • POST /billing/topups — required
  • POST /isp/orders/{id}/reset-password

Pagination

Cursor-based

List endpoints that can return more rows than fit in one response are cursor-paginated: pass limit to size a page, and cursor to fetch the next one. There is no page-number or offset parameter — the cursor is the only supported way to page forward.

Reading a page

Every paginated response includes has_more and next_cursor. When has_more is true, pass next_cursor back as the cursor query parameter to fetch the next page. When it's false, next_cursor is null — you're on the last page.

The cursor is opaque

Treat next_cursor as an opaque token — store and replay it verbatim. Its internal encoding is not part of the contract and may change without notice.

Where it applies

  • GET /usage/domains
  • GET /transactions
  • GET /generations
Tip: Pages are stable under concurrent inserts — a row added mid-scan cannot cause the next page to skip or repeat a row, unlike a naive offset-based page.

Errors

One envelope

Every error response shares one envelope. request_id echoes the response's X-Request-Id header — quote it in support requests. details is present only on 422 invalid_params (see Strict mode).

{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the proxies:generate scope.",
    "request_id": "req_a1b2c3d4"
  }
}
Error codeHTTP statusMeaning
unauthorized 401 Missing, malformed, or revoked API key.
account_disabled 403 Your account has been suspended.
insufficient_scope 403 The key used for this request does not have the scope this endpoint requires.
not_found 404 No resource exists at this id, or it does not belong to your account.
pool_not_available 404 The pool id is not available to your account, or does not exist.
invalid_request 400 The request could not be performed as sent — see `message` for the specific reason.
invalid_params 422 A parameter failed strict validation. Only returned when the request sends RF-Strict: 1 — otherwise the same problem is silently coerced instead.
invalid_country 422 The `country` value is not a supported ISO 3166-1 alpha-2 code. Call GET /pools for the list a pool accepts.
country_not_available 422 This pool cannot serve the requested `country`. Call GET /pools for the list it accepts.
rate_limited 429 You have exceeded the rate limit for this endpoint's group. Read Retry-After and back off.
too_many_keys 409 You already hold the maximum of 10 API keys. Returned by key creation in the dashboard Developer tab, not by any /api/v1 endpoint.
idempotency_key_reuse 409 The Idempotency-Key header was reused with a request body that does not match the original request — see Idempotency.
idempotency_in_progress 409
spend_cap_exceeded 402 This key has no monthly spend cap, or this request would exceed it. Create a new key with a higher (or any) cap.
card_not_available 409 No saved card is available for an off-session charge on this account. Use payment_method "checkout" instead.
payment_failed 402 The charge attempt was declined. See `message` for the reason; a card requiring authentication should retry via payment_method "checkout".
order_not_active 409 This order is not active, so it holds no IPs. Only an active or past-due order can be revealed or reset.
client_not_allowed 403
internal_error 500 The request could not be completed due to a server-side error. Safe to retry.
server_error 503

On a 429 rate_limited response, read the Retry-After header and back off — see Rate Limits for the per-group budgets and a backoff example.

Rate Limits

Per key · 60 s window

Rate limits are enforced per API key over a fixed 60-second window, not a rolling one — the count resets at the top of each window rather than sliding continuously, so a burst of requests that straddles a window boundary can briefly exceed the stated per-minute rate by up to roughly 2x. Endpoints are grouped by cost so heavy proxy generation or a billing write cannot starve read endpoints.

Endpoint groupEndpointsLimit
Generate POST /proxies 20 / min
Billing POST /balance/convert, POST /billing/topups 6 / min
ISP logins (per account) GET /isp/orders/{id}/ips, POST /isp/orders/{id}/reset-password 10 / min
Reads GET /me, GET /balance, GET /usage, GET /pools, GET /pools/{id}, GET /status, GET /usage/daily, GET /usage/domains, GET /transactions, GET /generations, GET /billing/topups/{id}, GET /isp/orders 120 / min

Response headers

Every response includes rate-limit headers so you can track consumption without hitting the limit:

HeaderDescription
RateLimit-LimitMaximum requests allowed in the current window.
RateLimit-RemainingRequests remaining before you hit the limit.
RateLimit-ResetSeconds remaining until the window resets (not a Unix timestamp).
Retry-AfterSeconds 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');
}

Code Examples

Pick your stack

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

Read balance

curl -X GET https://resifactory.net/api/v1/balance \
  -H "Authorization: Bearer rf_live_YOUR_KEY"

Versioning & Changelog

/api/v1 is frozen

The current API version is v1, served at /api/v1/. This path is meant to stay non-breaking — normally, a breaking change (removed field, changed semantics, altered authentication) ships under a new version path (e.g. /api/v2) with advance notice and a deprecation period, rather than under /api/v1 itself. There is one documented exception below: the balances type change dated 2026-08-06, marked Breaking rather than glossed over as routine, and mitigated by keeping the pre-change balances_gb shape live until its stated Sunset.

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

Spec

The OpenAPI 3.1 machine-readable spec is always up-to-date at:

/api/v1/openapi.json

Changelog

v1.3

Essential sticky sessions · November 2026

Essential holds a sticky session for at most 120 minutes, so a longer sessionDuration on Essential is now set to 120. POST /proxies returns session_duration 120 for such a batch, and GET /pools and GET /pools/{id} report session_minutes.max 120 for Essential. Premium and Mobile still hold a session for 1 to 1,440 minutes. Nothing is refused that was accepted before, and no field changed its name or type. GET /generations keeps the length that was asked for on batches made before this change, so an older Essential batch can report more than 120.

v1.2

ISP proxies · November 2026

New endpoints: GET /isp/orders and GET /isp/orders/{id}/ips (isp:read), and POST /isp/orders/{id}/reset-password (isp:write). Both scopes are opt-in. GET /isp/orders/{id}/ips returns every IP of an order with its login as JSON, or as a CSV or text download with format=csv or format=txt. Every call to it is recorded on your account, the same as Show credentials in the dashboard. The reveal and the reset share a budget of 10 requests a minute per account, across all of its keys. limits on GET /me and GET /status gains isp_per_min.

v1.1

Pool detail, status, usage history, billing, idempotency & pagination · August 2026

New endpoints: GET /pools/{id}, GET /status, GET /usage/daily, GET /usage/domains, GET /transactions, and GET /generations (usage:read); POST /balance/convert, POST /billing/topups, and GET /billing/topups/{id} behind a new billing:write scope. GET /usage is deprecated in favor of GET /usage/domains (date ranges, pagination) — it keeps working, adding an unbounded top-25 was never breaking. List endpoints are cursor-paginated (see Pagination). Mutating endpoints accept an Idempotency-Key header (see Idempotency), required on POST /billing/topups. Send RF-Strict: 1 to opt into strict parameter validation (see Strict mode) ahead of it becoming the default.

Breaking: balances on GET /balance/GET /me changed shape — it used to map pool id to a bare number (GB remaining); it now maps pool id to { gb, price_per_gb, currency }. If your integration reads balances[poolId] as a number, this breaks the day this deploys. Migrate to balances[poolId].gb, or keep reading the unchanged numeric shape from the deprecated balances_gb[poolId] until its Sunset date if you can't migrate immediately. Also breaking: the carrier body parameter and the socks5 protocol value are no longer accepted on POST /proxies. carrier-network targeting was withdrawn from the platform on 2026-07-22, and the parameter had been silently ignored since — removing it changes nothing about what a working integration does today. Nothing served socks5 at that time, so a proxy line generated with it could never actually connect; it returned on 2026-10-23 on port 1080, on every pool except Essential. The https protocol value was removed the same day for an unrelated reason — it wasn't served on every relay yet — and RESTORED 2026-08-06 by RF-16 once every relay served :7443, then withdrawn on 2026-08-31 (below).

Breaking, 2026-08-31 — https withdrawn: https is no longer accepted by POST /proxies. http (port 7777) is accepted on every pool, and since 2026-10-23 socks5 (port 1080) on every pool except Essential. The 2026-08-06 restore above was verified across the whole fleet on the day it shipped. On 2026-08-18 we rebuilt that fleet on new hardware, and the rebuild never installed the TLS certificate or opened the firewall rule the :7443 endpoint requires — so it stopped being reachable that day, while this page continued to advertise it for 13 days. Lines generated with https in that window could not connect. We have withdrawn the value rather than restore an endpoint whose certificates we cannot yet renew automatically. Migration: send protocol: "http" (or "socks5" on any pool except Essential) or omit the field — it defaults to http. Callers using the default lenient validation are unaffected: an unrecognised value falls back to http. Strict-mode callers sending protocol: "https" will now get 422 not_in_enum instead of 200. That is the only breaking case.

v1.0

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

Common answers
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), except on Essential, where a session holds at most 120 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.