Skip to content

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.

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.

RequestGET /profile
curl 'https://{api_host}/api/v1/profile' \
--header 'Authorization: Static {api_key}'
Response200 OK

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 →

RequestGET /orders
curl 'https://{api_host}/api/v1/orders' \
--get \
--header 'Authorization: Static {api_key}' \
--data-urlencode 'package__id={package_id}'
Response200 OK

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.

RequestPOST /users
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}"
}'
Response201 Created

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.

Both package_id and data are required. data is the amount to add, not the desired final balance.

RequestPOST /users/{user_id}/data/add
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
}'
Response202 Accepted

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.

Assigning a quota does not move traffic. Using a proxy does.
  1. 1 · Before allocationParent: 100 GiB

    Purchased data available to the whole order tree.

  2. 2 · Assign two quotasA: 80 GiB · B: 80 GiB

    Parent still has 100 GiB. The 160 GiB of quotas is not reserved.

  3. 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”
RequestPOST /proxies/generate
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"
}
}'
Response201 Created
{
"count": 1,
"proxies": [
{
"host": "proxy.example.test",
"port": 8000,
"protocol": "http",
"username": "package-elite",
"password": "example-proxy-password",
"connection_string": "http://package-elite:[email protected]:8000"
}
]
}

Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →

RequestGET https://api.ipify.org?format=json
curl 'https://api.ipify.org?format=json' \
--proxy '{proxy_url}'
Response200 OK
{
"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.

RequestGET /users/{user_id}/orders
curl 'https://{api_host}/api/v1/users/{user_id}/orders' \
--header 'Authorization: Static {api_key}'
Response200 OK

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 →

RequestGET /orders
curl 'https://{api_host}/api/v1/orders' \
--get \
--header 'Authorization: Static {api_key}' \
--data-urlencode 'package__id={package_id}'
Response200 OK

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.

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.