Retries and concurrent changes
Your backend can lose a response after the API has already created a resource. Do not turn “I did not receive a response” into “create it again.” Keep a durable local operation record and verify the platform state before repeating an uncertain write.
Keep enough state to recover
Section titled “Keep enough state to recover”Before creating a user, invoice, allocation or webhook, store an internal operation ID, the exact request, the intended customer and the current workflow step. After success, save the returned platform ID before starting the next step.
The official SDKs automatically protect supported writes during a small number of transient retries inside one running call. A process restart or a new manual call is a new attempt. If the original result is unknown, inspect the affected resource instead of blindly rerunning the write. Raw cURL and HTTP examples do not provide this automatic retry behavior.
Handle the result
Section titled “Handle the result”| Result | What your backend should do |
|---|---|
| Success | Store resource IDs and mark the local operation complete |
| SDK reports a final network failure | Treat the outcome as uncertain and inspect the affected resource before another write |
| Process stopped before saving the result | Resume from the local operation record and reconcile the platform state |
409 Conflict | Read the error details, respect Retry-After when present, and re-read state before retrying |
| Validation or permission error | Correct the underlying issue deliberately; do not loop on the same invalid request |
| Outcome cannot be established | Preserve the operation for review rather than issuing another paid invoice or quota increment |
For provider initialization errors such as 502, preserve any returned invoice_id. An invoice may already exist even though checkout setup failed. Inspect it and the documented payment-link operation before creating another purchase.
Avoid overwriting another actor’s edit
Section titled “Avoid overwriting another actor’s edit”Supported reads return a strong ETag. Save it and send it unchanged in If-Match on a supported update or deletion. The header is optional, but omitting it omits that concurrency check.
curl --request PATCH 'https://{api_host}/api/v1/users/{user_id}' \ --header 'Authorization: Static {api_key}' \ --header 'Content-Type: application/json' \ --header 'If-Match: {etag}' \ --data '{ "first_name": "Dana"}'import { ProxyRequestClient } from "@proxyrequest/sdk";
const client = ProxyRequestClient.withApiKey( "{api_key}", { baseUrl: "https://{api_host}/api/v1" });
const result = await client.users.update({ id: "{user_id}", body: { "first_name": "Dana" }, ifMatch: "{etag}",});from proxyrequest_sdk.models import PatchedUserUpdateRequestfrom 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.update( id=UUID("{user_id}"), body=PatchedUserUpdateRequest.from_dict({ "first_name": "Dana" }), if_match="{etag}", )<?phprequire __DIR__ . '/vendor/autoload.php';
use ProxyRequest\Client;use ProxyRequest\Dto\PatchedUserUpdateRequest;
$client = Client::withApiKey( '{api_key}', 'https://{api_host}/api/v1');
$result = $client->users()->update( id: '{user_id}', patchedUserUpdateRequest: new PatchedUserUpdateRequest([ 'firstName' => 'Dana' ]), ifMatch: '{etag}',);Selected fields
{ "id": "550e8400-e29b-41d4-a716-446655440001", "first_name": "Dana", "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": "Dana", "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 →
Replace {etag} with the complete ETag value from your read, including its double quotes.
If the resource changed, the API returns 412 Precondition Failed. Read the latest state and decide whether the edit is still appropriate before resubmitting. Do not silently retry with If-Match: * or remove the header. Weak validators (W/…) are not accepted as a substitute for a strong tag.
Supported conditional writes cover profile, users, orders, coupons and deletion of invoices/webhooks, as documented per operation. Data allocation is not an absolute-balance compare-and-set API, so read the current assigned total before recovering an uncertain increment or subtraction.
Reconcile, do not guess from balances
Section titled “Reconcile, do not guess from balances”Two valid traffic batches or purchases may arrive while you recover a timeout. A matching balance difference alone cannot prove which request succeeded. Use your internal operation record, returned resource IDs, invoice state and customer/package mapping together. Preserve an unresolved operation for review rather than automatically issuing a new paid invoice.
Retry safe reads with bounded exponential backoff and jitter; honor server retry guidance. A missing response body on a successful 204 is normal. Error text can be localized: branch on HTTP status and documented machine fields, not English wording. See API errors.