Reseller workflow
This quickstart gives one customer a 10 GiB quota on your existing shared pool. It does not buy them independent data. If your platform owns billing and should fund each customer’s own order, use hybrid and headless integration.
Before you start
Section titled “Before you start”You need a top-level reseller account, its backend-only API key, and a purchased root order for the selected package. The pool must have usable data for traffic to work. Creating a package or sub-user does not fund it.
Use a test deployment and cURL or a compatible official SDK for the API steps; keep cURL for the proxy smoke test. Prepare a unique customer username and a generated 8–128 character account password. Persist the request and current workflow step before each write.
curl 'https://{api_host}/api/v1/profile' \ --header 'Authorization: Static {api_key}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.profile.get();from proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.profile.get()<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->profile()->get();Selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440010", "username": "customer_demo_42", "is_reseller": true, "is_superuser": false, "orders": []}Show full responseShow selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440010", "username": "customer_demo_42", "email": "", "is_reseller": true, "is_marketer": false, "is_superuser": false, "date_joined": "2026-09-16T12:00:00Z", "date_joined_ts": 1789560000, "first_name": "", "last_name": "", "balance": 0, "language": "en", "country": "", "state": "", "city": "", "address": "", "zip": "", "company_name": "", "company_address": "", "company_city": "", "company_postal_code": "", "company_country": "", "company_vat_number": "", "allowed_ips": [], "blocked_domains": [], "connection_limit": 100, "parent_id": "", "sub_users": 0, "referrals": 0, "referral_id": "", "referral_code": "example42", "referral_data_earned": 0, "referral_data_pending": 0, "referral_balance_pending": 0, "referral_balance_earned": 0, "currency": { "code": "USD", "symbol": "$" }, "coupons": [], "orders": []}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
curl 'https://{api_host}/api/v1/orders' \ --get \ --header 'Authorization: Static {api_key}' \ --data-urlencode 'package__id={package_id}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.orders.list({ packageId: "{package_id}",});from proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.orders.list( package_id="{package_id}", )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->orders()->list( packageId: '{package_id}',);Selected fields
{ "count": 1, "next": null, "previous": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440011", "package": { "id": "550e8400-e29b-41d4-a716-446655440002" }, "data": 107374182400, "data_remaining": 107374182400, "data_spent": 0, "ledgers": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data_remaining": 107374182400, "expires": "2026-10-16T12:00:00Z" } ] } ]}Show full responseShow selected fields
{ "count": 1, "next": null, "previous": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440011", "is_auto_renewal": false, "auto_renewal_percentage": 0, "auto_renewal_data": 0, "package": { "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Residential example", "alias": "elite", "is_unlimited_data": false, "targeting_options": { "package": "package", "split_char": "-", "value_char": "-", "continent": "continent", "country": "country", "region": "region", "city": "city", "asn": "asn", "isp": "isp", "username": "username", "pool": "pool", "location": "location", "location_format": "", "session": "sid", "session_mode_tag": "", "session_ttl": "ttl", "session_ttl_format": 1, "os": "os", "os_combined": false, "os_split_char": "-", "os_linux": "linux", "os_windows": "windows", "os_ios": "ios", "os_macos": "macos", "os_android": "android" } }, "proxy_password": "example-proxy-password", "proxy_password_reset": null, "pools": [], "data": 107374182400, "data_remaining": 107374182400, "data_spent": 0, "ledgers": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data_remaining": 107374182400, "expires": "2026-10-16T12:00:00Z" } ], "latest_data_top_up": 107374182400, "latest_data_top_up_date": "2026-09-16T12:00:00Z", "data_updated": "2026-09-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z" } ]}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
Replace {api_host} with your API hostname (without https://), {api_key} with your reseller key, and {package_id} with your purchased package ID.
Confirm the role and matching root order. If it is absent or depleted, first use a pending invoice or wallet purchase to fund your own account. Omit invoice user_id for your own purchase. An ordinary reseller cannot set status=paid.
1. Create a managed customer
Section titled “1. Create a managed customer”curl --request POST 'https://{api_host}/api/v1/users' \ --header 'Authorization: Static {api_key}' \ --header 'Content-Type: application/json' \ --data '{ "username": "{username}", "password": "{account_password}"}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.users.create({ body: { "username": "{username}", "password": "{account_password}", "is_reseller": false, "is_top_level": false },});from proxyrequest_sdk.models import UserCreateRequestfrom proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.users.create( body=UserCreateRequest.from_dict({ "username": "{username}", "password": "{account_password}" }), )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;use ProxyRequest\Dto\UserCreateRequest;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->users()->create( userCreateRequest: new UserCreateRequest([ 'username' => '{username}', 'password' => '{account_password}' ]),);Selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440001", "username": "customer_demo_42", "is_reseller": false, "is_superuser": false, "orders": []}Show full responseShow selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440001", "username": "customer_demo_42", "email": "", "is_reseller": false, "is_marketer": false, "is_superuser": false, "date_joined": "2026-09-16T12:00:00Z", "date_joined_ts": 1789560000, "first_name": "", "last_name": "", "balance": 0, "language": "en", "country": "", "state": "", "city": "", "address": "", "zip": "", "company_name": "", "company_address": "", "company_city": "", "company_postal_code": "", "company_country": "", "company_vat_number": "", "allowed_ips": [], "blocked_domains": [], "connection_limit": 100, "parent_id": "550e8400-e29b-41d4-a716-446655440010", "sub_users": 0, "referrals": 0, "referral_id": "", "referral_code": "example42", "referral_data_earned": 0, "referral_data_pending": 0, "referral_balance_pending": 0, "referral_balance_earned": 0, "currency": { "code": "USD", "symbol": "$" }, "coupons": [], "orders": []}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
Expect 201. Save the returned id with your customer record. Leaving is_top_level at its default false and omitting package_id and data creates only a sub-user identity under you. It has no package quota yet.
2. Assign 10 GiB
Section titled “2. Assign 10 GiB”Both package_id and data are required. data is the amount to add, not the desired final balance.
curl --request POST 'https://{api_host}/api/v1/users/{user_id}/data/add' \ --header 'Authorization: Static {api_key}' \ --header 'Content-Type: application/json' \ --data '{ "package_id": "{package_id}", "data": 10737418240}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.users.addData({ id: "{user_id}", body: { "package_id": "{package_id}", "data": 10737418240 },});from proxyrequest_sdk.models import AddDataRequestfrom uuid import UUIDfrom proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.users.add_data( id=UUID("{user_id}"), body=AddDataRequest.from_dict({ "package_id": "{package_id}", "data": 10737418240 }), )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;use ProxyRequest\Dto\AddDataRequest;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->users()->addData( id: '{user_id}', addDataRequest: new AddDataRequest([ 'packageId' => '{package_id}', 'data' => 10737418240 ]),);Selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440003", "package": { "id": "550e8400-e29b-41d4-a716-446655440002" }, "data": 10737418240, "data_remaining": 10737418240, "data_spent": 0, "ledgers": []}Show full responseShow selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440003", "is_auto_renewal": false, "auto_renewal_percentage": 0, "auto_renewal_data": 0, "package": { "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Residential example", "alias": "elite", "is_unlimited_data": false, "targeting_options": { "package": "package", "split_char": "-", "value_char": "-", "continent": "continent", "country": "country", "region": "region", "city": "city", "asn": "asn", "isp": "isp", "username": "username", "pool": "pool", "location": "location", "location_format": "", "session": "sid", "session_mode_tag": "", "session_ttl": "ttl", "session_ttl_format": 1, "os": "os", "os_combined": false, "os_split_char": "-", "os_linux": "linux", "os_windows": "windows", "os_ios": "ios", "os_macos": "macos", "os_android": "android" } }, "proxy_password": "example-proxy-password", "proxy_password_reset": null, "pools": [], "data": 10737418240, "data_remaining": 10737418240, "data_spent": 0, "ledgers": [], "latest_data_top_up": 0, "latest_data_top_up_date": null, "data_updated": "2026-09-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z"}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
Expect 202. The response shows the new allowance, not a purchase.
Store the order ID and confirmed allocation. Empty ledgers is expected for this virtual child order: the purchased buckets belong to your root order. Your pool’s balance has not changed.
- 1 · Before allocationParent: 100 GiB
Purchased data available to the whole order tree.
- 2 · Assign two quotasA: 80 GiB · B: 80 GiB
Parent still has 100 GiB. The 160 GiB of quotas is not reserved.
- 3 · A uses 10 GiBParent: 90 GiB
A has 70 GiB left. B still has 80 GiB. Both depend on the same parent pool.
Access needs personal quota and usable shared data. Either can run out first.
3. Generate and test the customer’s proxy
Section titled “3. Generate and test the customer’s proxy”curl --request POST 'https://{api_host}/api/v1/proxies/generate' \ --header 'Authorization: Static {api_key}' \ --header 'Content-Type: application/json' \ --data '{ "user_id": "{user_id}", "package_id": "{package_id}", "quantity": 1, "connection": { "protocol": "http" }}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
// Keep format tokens unchanged; the generator expands them.const result = await client.proxies.generate({ body: { "user_id": "{user_id}", "package_id": "{package_id}", "quantity": 1, "connection": { "protocol": "http", "format": "{protocol}://{username}:{password}@{host}:{port}" } },});from proxyrequest_sdk.models import GenerateProxyRequestfrom proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.proxies.generate( body=GenerateProxyRequest.from_dict({ "user_id": "{user_id}", "package_id": "{package_id}", "quantity": 1, "connection": { "protocol": "http" } }), )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;use ProxyRequest\Dto\GenerateProxyRequest;use ProxyRequest\Dto\ProxyGenerationConnectionRequest;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->proxies()->generate( generateProxyRequest: new GenerateProxyRequest([ 'userId' => '{user_id}', 'packageId' => '{package_id}', 'quantity' => 1, 'connection' => new ProxyGenerationConnectionRequest([ 'protocol' => 'http' ]) ]),);{ "count": 1, "proxies": [ { "host": "proxy.example.test", "port": 8000, "protocol": "http", "username": "package-elite", "password": "example-proxy-password", } ]}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
curl 'https://api.ipify.org?format=json' \ --proxy '{proxy_url}'{ "ip": "203.0.113.42"}Replace placeholders in braces with your values. Responses use synthetic example data.
Use the returned connection_string as {proxy_url}. Generation returns 201 with a proxies array; the last request should return an exit IP. Pass user_id so the credentials belong to the customer rather than yourself. Keep generated secrets out of logs and deliver only that customer’s credentials.
4. Verify both balances
Section titled “4. Verify both balances”curl 'https://{api_host}/api/v1/users/{user_id}/orders' \ --header 'Authorization: Static {api_key}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.users.listOrders({ idPath: "{user_id}",});from uuid import UUIDfrom proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.users.list_orders( id_path=UUID("{user_id}"), )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->users()->listOrders( id: '{user_id}',);Selected fields
{ "count": 1, "next": null, "previous": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440003", "package": { "id": "550e8400-e29b-41d4-a716-446655440002" }, "data": 10737418240, "data_remaining": 10737417216, "data_spent": 1024, "ledgers": [] } ]}Show full responseShow selected fields
{ "count": 1, "next": null, "previous": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440003", "is_auto_renewal": false, "auto_renewal_percentage": 0, "auto_renewal_data": 0, "package": { "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Residential example", "alias": "elite", "is_unlimited_data": false, "targeting_options": { "package": "package", "split_char": "-", "value_char": "-", "continent": "continent", "country": "country", "region": "region", "city": "city", "asn": "asn", "isp": "isp", "username": "username", "pool": "pool", "location": "location", "location_format": "", "session": "sid", "session_mode_tag": "", "session_ttl": "ttl", "session_ttl_format": 1, "os": "os", "os_combined": false, "os_split_char": "-", "os_linux": "linux", "os_windows": "windows", "os_ios": "ios", "os_macos": "macos", "os_android": "android" } }, "proxy_password": "example-proxy-password", "proxy_password_reset": null, "pools": [], "data": 10737418240, "data_remaining": 10737417216, "data_spent": 1024, "ledgers": [], "latest_data_top_up": 0, "latest_data_top_up_date": null, "data_updated": "2026-09-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z" } ]}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
curl 'https://{api_host}/api/v1/orders' \ --get \ --header 'Authorization: Static {api_key}' \ --data-urlencode 'package__id={package_id}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.orders.list({ packageId: "{package_id}",});from proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.orders.list( package_id="{package_id}", )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->orders()->list( packageId: '{package_id}',);Selected fields
{ "count": 1, "next": null, "previous": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440011", "package": { "id": "550e8400-e29b-41d4-a716-446655440002" }, "data": 107374182400, "data_remaining": 107374181376, "data_spent": 0, "ledgers": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data_remaining": 107374181376, "expires": "2026-10-16T12:00:00Z" } ] } ]}Show full responseShow selected fields
{ "count": 1, "next": null, "previous": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440011", "is_auto_renewal": false, "auto_renewal_percentage": 0, "auto_renewal_data": 0, "package": { "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Residential example", "alias": "elite", "is_unlimited_data": false, "targeting_options": { "package": "package", "split_char": "-", "value_char": "-", "continent": "continent", "country": "country", "region": "region", "city": "city", "asn": "asn", "isp": "isp", "username": "username", "pool": "pool", "location": "location", "location_format": "", "session": "sid", "session_mode_tag": "", "session_ttl": "ttl", "session_ttl_format": 1, "os": "os", "os_combined": false, "os_split_char": "-", "os_linux": "linux", "os_windows": "windows", "os_ios": "ios", "os_macos": "macos", "os_android": "android" } }, "proxy_password": "example-proxy-password", "proxy_password_reset": null, "pools": [], "data": 107374182400, "data_remaining": 107374181376, "data_spent": 0, "ledgers": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data_remaining": 107374181376, "expires": "2026-10-16T12:00:00Z" } ], "latest_data_top_up": 107374182400, "latest_data_top_up_date": "2026-09-16T12:00:00Z", "data_updated": "2026-09-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z" } ]}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
These synthetic responses assume 1,024 bytes of test traffic; your actual byte count will differ. After accounting batches arrive, the child’s data_spent increases and its personal remaining quota falls. The root’s usable data_remaining also falls. A child request does not increase the root user’s own data_spent.
Read analytics with the correct customer/package scope, then add signed reseller webhooks for timely UI updates. Do not calculate customer usage from webhook delivery counts.
Recovery, changes and removal
Section titled “Recovery, changes and removal”A timeout is not proof that allocation failed. Read the child’s order and compare its assigned total before deciding whether another increment is needed. Follow retries and recovery for other uncertain writes.
For a later top-up, create a new local operation and send the number of additional bytes. To reduce quota, use data subtraction; it does not refund money or replenish your pool. Do not try these endpoints on an independently purchased order.
When a customer leaves, stop new allocations, apply the permitted access controls and retain the mapping until cleanup is confirmed. User/order deletion is destructive, not a balance-reconciliation shortcut.