Hybrid and headless integration
Keep your identity, checkout, subscriptions and UI. Use ProxyRequest to provision proxy access and account for traffic. This quickstart creates independent purchased data for one customer; it does not allocate from a reseller’s pool. For that model, use the reseller quickstart.
Before you start
Section titled “Before you start”- Use a test deployment with the packages you want to sell and a working gateway.
- Prepare a finite residential package whose pricing accepts 10 GiB. Store its returned
package_id; a product name is not an API identifier. - Use a backend-only service account with
is_superuser=true,is_reseller=trueand no parent. The superuser flag permitsstatus=paid; the reseller flag is also required when sending invoiceuser_id. - Install cURL or a compatible official SDK for the API steps; keep cURL for the proxy smoke test. Use a generated customer password of 8–128 characters. Never expose the service key in your frontend.
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": true, "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": true, "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 →
Replace {api_host} with your API hostname (without https://), and {api_key} with the test service key. Use your configured {package_id} and a unique {username} with a generated {account_password}. Check the profile before continuing.
Persist each write body and the current workflow step before sending it. Store every returned resource ID before moving to the next step.
1. Create identity without allocating data
Section titled “1. Create identity without allocating data”POST /users returns 201. Leave is_top_level at its default false to create a managed identity under the service account. Omit package_id and data so identity creation does not allocate a shared-pool quota. The separate invoice will buy this customer’s own root order: managed identity does not mean shared traffic.
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 →
Save the returned id as {user_id} in your customer mapping immediately. The response contains the new account, not proof of a funded order.
2. Record a confirmed external purchase
Section titled “2. Record a confirmed external purchase”Only do this after your backend has verified the external payment or approved a deliberate test grant. POST /invoices with gateway: manual does not charge your external provider. It records a purchase and provisions the corresponding entitlement. The package still determines the platform price; this request is not an arbitrary price override.
curl --request POST 'https://{api_host}/api/v1/invoices' \ --header 'Authorization: Static {api_key}' \ --header 'Content-Type: application/json' \ --data '{ "user_id": "{user_id}", "package_id": "{package_id}", "data": 10737418240, "gateway": "manual", "status": "paid"}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.invoices.create({ body: { "user_id": "{user_id}", "package_id": "{package_id}", "data": 10737418240, "gateway": "manual", "status": "paid" },});from proxyrequest_sdk.models import InvoiceCreateRequestfrom proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.invoices.create( body=InvoiceCreateRequest.from_dict({ "user_id": "{user_id}", "package_id": "{package_id}", "data": 10737418240, "gateway": "manual", "status": "paid" }), )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;use ProxyRequest\Dto\InvoiceCreateRequest;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->invoices()->create( invoiceCreateRequest: new InvoiceCreateRequest([ 'userId' => '{user_id}', 'packageId' => '{package_id}', 'data' => 10737418240, 'gateway' => 'manual', 'status' => 'paid' ]),);Selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440004", "user_id": "550e8400-e29b-41d4-a716-446655440001", "status": "paid", "data": 10737418240, "price_total": 2500, "payment_url": "", "currency": "USD"}Show full responseShow selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440004", "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" } }, "country": null, "user_id": "550e8400-e29b-41d4-a716-446655440001", "coupon": null, "payment_amount": 2500, "payment_currency": "USD", "fx_market_rate": "1", "fx_effective_rate": "1", "fx_markup_percent": "0", "fx_quoted_at": "2026-09-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z", "type": "residential", "is_one_time": false, "is_payout": false, "internal_id": "EXAMPLE-00042", "status": "paid", "description": "", "connection_limit": 100, "quantity": 0, "data": 10737418240, "balance": 0, "price_total": 2500, "gateway": "manual", "payment_url": "", "currency": "USD", "provider_checkout_id": "", "provider_payment_id": "", "checkout_status": "not_required", "vat": 0, "company_name": "", "company_address": "", "company_city": "", "company_postal_code": "", "company_registration_number": "", "company_vat_number": "", "paid": "2026-09-16T12:00:00Z"}Replace placeholders in braces with your values. Responses use synthetic example data.Install SDKsAPI reference →
Expect 201 with status: "paid" and data: 10737418240. Save the returned id as {invoice_id} against your purchase. A non-superuser sending paid receives 403; staff access or reseller status alone is insufficient. Other callers can use the default pending purchase flow.
To buy for the service account itself, omit user_id rather than sending its own ID. To give an existing managed sub-user independent data, the target must not already have a virtual order for this package. Read the two accounting models before mixing workflows.
3. Verify the paid invoice and resulting order
Section titled “3. Verify the paid invoice and resulting order”curl 'https://{api_host}/api/v1/invoices/{invoice_id}' \ --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.invoices.get({ id: "{invoice_id}",});from proxyrequest_sdk import Client
with Client.with_api_key( "{api_key}", base_url="https://{api_host}/api/v1") as client: result = client.invoices.get( id="{invoice_id}", )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->invoices()->get( id: '{invoice_id}',);Selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440004", "user_id": "550e8400-e29b-41d4-a716-446655440001", "status": "paid", "data": 10737418240, "price_total": 2500, "payment_url": "", "currency": "USD"}Show full responseShow selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440004", "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" } }, "country": null, "user_id": "550e8400-e29b-41d4-a716-446655440001", "coupon": null, "payment_amount": 2500, "payment_currency": "USD", "fx_market_rate": "1", "fx_effective_rate": "1", "fx_markup_percent": "0", "fx_quoted_at": "2026-09-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z", "type": "residential", "is_one_time": false, "is_payout": false, "internal_id": "EXAMPLE-00042", "status": "paid", "description": "", "connection_limit": 100, "quantity": 0, "data": 10737418240, "balance": 0, "price_total": 2500, "gateway": "manual", "payment_url": "", "currency": "USD", "provider_checkout_id": "", "provider_payment_id": "", "checkout_status": "not_required", "vat": 0, "company_name": "", "company_address": "", "company_city": "", "company_postal_code": "", "company_registration_number": "", "company_vat_number": "", "paid": "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/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": 10737418240, "data_spent": 0, "ledgers": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data": 10737418240, "data_remaining": 10737418240, "expires": "2026-10-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z" } ] } ]}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": 10737418240, "data_spent": 0, "ledgers": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data": 10737418240, "data_remaining": 10737418240, "expires": "2026-10-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z" } ], "latest_data_top_up": 10737418240, "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 →
For a fresh finite 10 GiB purchase, expect a matching order, 10,737,418,240 remaining bytes and a purchased ledger. Follow next if the order is not on the first page. Persist the matching order_id.
Fulfillment normally happens during payment processing, but recovery can be asynchronous. If the invoice is paid and the order is not ready, poll these reads with bounded backoff; after your application deadline, mark the purchase reconcile_required and investigate. Do not create another paid invoice to repair an uncertain result. There is no invoice.paid accounting webhook to wait for.
4. Generate credentials and make a request
Section titled “4. Generate credentials and make a request”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.
Generation returns 201 and a proxies array containing host, port, username, password and connection string. The final request should return an exit IP. Use the returned connection_string as {proxy_url}. Keep it and the generated credentials out of logs: they contain a reusable secret. Generation alone is not a connectivity test.
The generator accepts user_id only for the caller’s own sub-user, including when the caller is a superuser. That is why this quickstart uses managed identity with independently purchased data. If you instead create a top-level customer with is_top_level: true, generate using that customer’s own authenticated context and omit user_id; the global service key cannot bypass this generator ownership check.
Start without optional targeting, then use catalog and proxy generation to add country or continent selection. A session TTL is in seconds; omit session for rotating eligibility.
5. Verify usage and keep durable mappings
Section titled “5. Verify usage and keep durable mappings”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": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data": 10737418240, "data_remaining": 10737417216, "expires": "2026-10-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z" } ] } ]}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": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "data": 10737418240, "data_remaining": 10737417216, "expires": "2026-10-16T12:00:00Z", "updated": "2026-09-16T12:00:00Z", "created": "2026-09-16T12:00:00Z" } ], "latest_data_top_up": 10737418240, "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 →
This example assumes 1,024 bytes were counted after the test. Your actual byte count will differ.
Read the customer’s order again after accounting batches arrive. A controlled request should increase its usage and reduce usable data. For exact reporting windows and customer filters, use analytics. Signed webhooks keep your UI current but are not your payment journal.
| Local record | Persisted ProxyRequest mapping |
|---|---|
| Customer | user_id, local customer reference and synchronization state |
| Product | package_id, configured pricing and byte-unit convention |
| Customer/product access | order_id; reused by later purchases of the same package |
| Each purchase | invoice_id, exact request and verified outcome |
Several local purchases or subscriptions may point to one user/package order. Keep their commercial histories separately. Optional user meta can hold non-sensitive correlation values; it does not replace this mapping or API IDs.
Recovery and cancellation
Section titled “Recovery and cancellation”The official SDK handles short transient failures during a running call. If the call still fails or your process restarts, do not blindly repeat a paid invoice: inspect the visible invoice and order first. See retries and recovery and keep local pending, active, reconcile_required and deprovisioning states.
Subscription renewal and cancellation remain your business logic. Another paid purchase tops up data but does not reset all usage counters. A refund in your payment provider does not automatically reverse a ProxyRequest grant; invoice deletion is not a refund API. Revoke appropriate access under your policy and retain mappings for reconciliation. Never apply shared-quota subtraction to an independently purchased order.